// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine Master // License Agreement. #include "device_files.h" #include #include #include "certificate_provisioning.h" #include "file_store.h" #include "license_protocol.pb.h" #include "log.h" #include "privacy_crypto.h" #include "properties.h" #include "string_conversions.h" #include "wv_cdm_constants.h" // Protobuf generated classes. using video_widevine_client::sdk::DeviceCertificate; using video_widevine_client::sdk::HashedFile; using video_widevine_client::sdk::HlsAttributes; using video_widevine_client::sdk::HlsAttributes_Method_AES_128; using video_widevine_client::sdk::HlsAttributes_Method_SAMPLE_AES; using video_widevine_client::sdk::License; using video_widevine_client::sdk::License_LicenseState_ACTIVE; using video_widevine_client::sdk::License_LicenseState_RELEASING; using video_widevine_client::sdk::NameValue; using video_widevine_client::sdk::UsageInfo; using video_widevine_client::sdk::UsageInfo_ProviderSession; using video_widevine_client::sdk::UsageTableInfo; using video_widevine_client::sdk::UsageTableInfo_UsageEntryInfo; using video_widevine_client::sdk:: UsageTableInfo_UsageEntryInfo_UsageEntryStorage_LICENSE; using video_widevine_client::sdk:: UsageTableInfo_UsageEntryInfo_UsageEntryStorage_UNKNOWN; using video_widevine_client::sdk:: UsageTableInfo_UsageEntryInfo_UsageEntryStorage_USAGE_INFO; // Stringify turns macro arguments into static C strings. // Example: STRINGIFY(this_argument) -> "this_argument" #define STRINGIFY(PARAM...) #PARAM #define RETURN_FALSE_IF_NULL(PARAM) \ if ((PARAM) == nullptr) { \ LOGE("Output parameter |" STRINGIFY(PARAM) "| not provided"); \ return false; \ } #define RETURN_FALSE_WITH_RESULT_IF_NULL(PARAM, result) \ if ((PARAM) == nullptr) { \ LOGE("Output parameter |" STRINGIFY(PARAM) "| not provided"); \ *result = kParameterNull; \ return false; \ } #define RETURN_FALSE_IF_UNINITIALIZED() \ if (!initialized_) { \ LOGE("Device files is not initialized"); \ return false; \ } #define RETURN_FALSE_WITH_RESULT_IF_UNINITIALIZED(result) \ if (!initialized_) { \ LOGE("Device files is not initialized"); \ *result = kObjectNotInitialized; \ return false; \ } namespace { const char kCertificateFileName[] = "cert.bin"; const char kHlsAttributesFileNameExt[] = ".hal"; const char kUsageInfoFileNamePrefix[] = "usage"; const char kUsageInfoFileNameExt[] = ".bin"; const char kLicenseFileNameExt[] = ".lic"; const char kEmptyFileName[] = ""; const char kUsageTableFileName[] = "usgtable.bin"; const char kWildcard[] = "*"; } // namespace namespace wvcdm { // static std::set DeviceFiles::reserved_license_ids_; DeviceFiles::DeviceFiles(FileSystem* file_system) : file_system_(file_system), security_level_(kSecurityLevelUninitialized), initialized_(false) {} DeviceFiles::~DeviceFiles() {} bool DeviceFiles::Init(CdmSecurityLevel security_level) { if (!file_system_) { LOGE("Invalid FileSystem given"); return false; } std::string path; if (!Properties::GetDeviceFilesBasePath(security_level, &path)) { LOGE("Unsupported security level: %d", security_level); return false; } security_level_ = security_level; initialized_ = true; return true; } bool DeviceFiles::StoreCertificate(const std::string& certificate, const std::string& wrapped_private_key) { RETURN_FALSE_IF_UNINITIALIZED(); // Fill in file information video_widevine_client::sdk::File file; file.set_type(video_widevine_client::sdk::File::DEVICE_CERTIFICATE); file.set_version(video_widevine_client::sdk::File::VERSION_1); DeviceCertificate* device_certificate = file.mutable_device_certificate(); device_certificate->set_certificate(certificate); device_certificate->set_wrapped_private_key(wrapped_private_key); std::string serialized_file; file.SerializeToString(&serialized_file); return StoreFileWithHash(GetCertificateFileName(), serialized_file) == kNoError; } bool DeviceFiles::RetrieveCertificate(std::string* certificate, std::string* wrapped_private_key, std::string* serial_number, uint32_t* system_id) { RETURN_FALSE_IF_UNINITIALIZED(); video_widevine_client::sdk::File file; if (RetrieveHashedFile(GetCertificateFileName(), &file) != kNoError) { LOGE("Unable to retrieve certificate file"); return false; } if (file.type() != video_widevine_client::sdk::File::DEVICE_CERTIFICATE) { LOGE("Certificate file is of incorrect file type: type = %d", static_cast(file.type())); return false; } if (file.version() != video_widevine_client::sdk::File::VERSION_1) { LOGE("Certificate file is of incorrect file version: version = %d", static_cast(file.version())); return false; } if (!file.has_device_certificate()) { LOGE("Certificate not present"); return false; } DeviceCertificate device_certificate = file.device_certificate(); *certificate = device_certificate.certificate(); *wrapped_private_key = device_certificate.wrapped_private_key(); return CertificateProvisioning::ExtractDeviceInfo( device_certificate.certificate(), serial_number, system_id); } bool DeviceFiles::HasCertificate() { RETURN_FALSE_IF_UNINITIALIZED(); return FileExists(GetCertificateFileName()); } bool DeviceFiles::RemoveCertificate() { RETURN_FALSE_IF_UNINITIALIZED() return RemoveFile(GetCertificateFileName()); } bool DeviceFiles::StoreLicense(const CdmLicenseData& license_data, ResponseType* result) { RETURN_FALSE_IF_NULL(result); *result = kNoError; RETURN_FALSE_WITH_RESULT_IF_UNINITIALIZED(result); // Fill in file information video_widevine_client::sdk::File file; file.set_type(video_widevine_client::sdk::File::LICENSE); file.set_version(video_widevine_client::sdk::File::VERSION_1); License* license = file.mutable_license(); switch (license_data.state) { case kLicenseStateActive: license->set_state(License_LicenseState_ACTIVE); break; case kLicenseStateReleasing: license->set_state(License_LicenseState_RELEASING); break; default: LOGE("Unknown license state: %d", static_cast(license_data.state)); *result = kUnknownLicenseState; return false; break; } license->set_pssh_data(license_data.pssh_data); license->set_license_request(license_data.license_request); license->set_license(license_data.license); license->set_renewal_request(license_data.license_renewal_request); license->set_renewal(license_data.license_renewal); license->set_release_server_url(license_data.release_server_url); license->set_playback_start_time(license_data.playback_start_time); license->set_last_playback_time(license_data.last_playback_time); license->set_grace_period_end_time(license_data.grace_period_end_time); for (CdmAppParameterMap::const_iterator iter = license_data.app_parameters.cbegin(); iter != license_data.app_parameters.cend(); ++iter) { NameValue* app_params = license->add_app_parameters(); app_params->set_name(iter->first); app_params->set_value(iter->second); } license->set_usage_entry(license_data.usage_entry); license->set_usage_entry_number(license_data.usage_entry_number); std::string serialized_file; file.SerializeToString(&serialized_file); reserved_license_ids_.erase(license_data.key_set_id); *result = StoreFileWithHash(license_data.key_set_id + kLicenseFileNameExt, serialized_file); return *result == kNoError; } bool DeviceFiles::RetrieveLicense(const std::string& key_set_id, CdmLicenseData* license_data, ResponseType* result) { // This check must be made first as the RETURN_FALSE_IF_NULL() macro // will assign kParameterNull to |result|. if (result == nullptr) { LOGE("Output parameter |result| not provided"); return false; } RETURN_FALSE_WITH_RESULT_IF_UNINITIALIZED(result); RETURN_FALSE_WITH_RESULT_IF_NULL(license_data, result); video_widevine_client::sdk::File file; *result = RetrieveHashedFile(key_set_id + kLicenseFileNameExt, &file); if (*result != kNoError) { LOGE("Unable to retrieve key set license file: result = %d", static_cast(*result)); return false; } if (file.type() != video_widevine_client::sdk::File::LICENSE) { LOGE("Incorrect file type: type = %d, expected_type = %d", static_cast(file.type()), static_cast(video_widevine_client::sdk::File::LICENSE)); *result = kIncorrectFileType; return false; } if (file.version() != video_widevine_client::sdk::File::VERSION_1) { LOGE("Incorrect file version: version = %d, expected_version = %d", static_cast(file.version()), static_cast(video_widevine_client::sdk::File::VERSION_1)); *result = kIncorrectFileVersion; return false; } if (!file.has_license()) { LOGE("License not present"); *result = kLicenseNotPresent; return false; } license_data->key_set_id = key_set_id; License license = file.license(); switch (license.state()) { case License_LicenseState_ACTIVE: license_data->state = kLicenseStateActive; break; case License_LicenseState_RELEASING: license_data->state = kLicenseStateReleasing; break; default: LOGW("Unrecognized license state: %d", static_cast(license.state())); license_data->state = kLicenseStateUnknown; break; } license_data->pssh_data = license.pssh_data(); license_data->license_request = license.license_request(); license_data->license = license.license(); license_data->license_renewal_request = license.renewal_request(); license_data->license_renewal = license.renewal(); license_data->release_server_url = license.release_server_url(); license_data->playback_start_time = license.playback_start_time(); license_data->last_playback_time = license.last_playback_time(); license_data->grace_period_end_time = license.grace_period_end_time(); for (int i = 0; i < license.app_parameters_size(); ++i) { license_data->app_parameters[license.app_parameters(i).name()] = license.app_parameters(i).value(); } license_data->usage_entry = license.usage_entry(); license_data->usage_entry_number = license.usage_entry_number(); return true; } bool DeviceFiles::DeleteLicense(const std::string& key_set_id) { RETURN_FALSE_IF_UNINITIALIZED(); return RemoveFile(key_set_id + kLicenseFileNameExt); } bool DeviceFiles::ListLicenses(std::vector* key_set_ids) { RETURN_FALSE_IF_UNINITIALIZED(); RETURN_FALSE_IF_NULL(key_set_ids); // Get list of filenames std::vector filenames; if (!ListFiles(&filenames)) { return false; } // Scan list of returned filenames, remove extension, and return // as a list of key_set_ids. key_set_ids->clear(); for (size_t i = 0; i < filenames.size(); i++) { std::string* name = &filenames[i]; std::size_t pos = name->find(kLicenseFileNameExt); if (pos == std::string::npos) { // Skip this file - extension does not match continue; } // Store filename (minus extension). This should be a key set ID. key_set_ids->push_back(name->substr(0, pos)); } return true; } bool DeviceFiles::DeleteAllLicenses() { RETURN_FALSE_IF_UNINITIALIZED(); return RemoveFile(std::string(kWildcard) + kLicenseFileNameExt); } bool DeviceFiles::DeleteAllFiles() { RETURN_FALSE_IF_UNINITIALIZED(); // We pass an empty string to RemoveFile to delete the device files base // directory itself. return RemoveFile(kEmptyFileName); } bool DeviceFiles::LicenseExists(const std::string& key_set_id) { RETURN_FALSE_IF_UNINITIALIZED(); return reserved_license_ids_.count(key_set_id) || FileExists(key_set_id + kLicenseFileNameExt); } bool DeviceFiles::ReserveLicenseId(const std::string& key_set_id) { RETURN_FALSE_IF_UNINITIALIZED(); reserved_license_ids_.insert(key_set_id); return true; } bool DeviceFiles::UnreserveLicenseId(const std::string& key_set_id) { RETURN_FALSE_IF_UNINITIALIZED(); reserved_license_ids_.erase(key_set_id); return true; } bool DeviceFiles::StoreUsageInfo(const std::string& provider_session_token, const CdmKeyMessage& key_request, const CdmKeyResponse& key_response, const std::string& usage_info_file_name, const std::string& key_set_id, const std::string& usage_entry, uint32_t usage_entry_number) { RETURN_FALSE_IF_UNINITIALIZED(); video_widevine_client::sdk::File file; if (!FileExists(usage_info_file_name)) { file.set_type(video_widevine_client::sdk::File::USAGE_INFO); file.set_version(video_widevine_client::sdk::File::VERSION_1); } else { if (RetrieveHashedFile(usage_info_file_name, &file) != kNoError) { LOGE("Unable to retrieve usage info file"); return false; } } UsageInfo* usage_info = file.mutable_usage_info(); UsageInfo_ProviderSession* provider_session = usage_info->add_sessions(); provider_session->set_token(provider_session_token.data(), provider_session_token.size()); provider_session->set_license_request(key_request.data(), key_request.size()); provider_session->set_license(key_response.data(), key_response.size()); provider_session->set_key_set_id(key_set_id.data(), key_set_id.size()); provider_session->set_usage_entry(usage_entry); provider_session->set_usage_entry_number(usage_entry_number); std::string serialized_file; file.SerializeToString(&serialized_file); return StoreFileWithHash(usage_info_file_name, serialized_file) == kNoError; } bool DeviceFiles::ListUsageIds( const std::string& app_id, std::vector* ksids, std::vector* provider_session_tokens) { RETURN_FALSE_IF_UNINITIALIZED(); if (ksids == nullptr && provider_session_tokens == nullptr) { LOGE( "Both output parameters |ksids| and |provider_session_tokens| are " "not provided"); return false; } // Empty or non-existent file == no usage records. std::string file_name = GetUsageInfoFileName(app_id); if (!FileExists(file_name) || GetFileSize(file_name) == 0) { if (ksids != nullptr) ksids->clear(); if (provider_session_tokens != nullptr) provider_session_tokens->clear(); return true; } video_widevine_client::sdk::File file; if (RetrieveHashedFile(file_name, &file) != kNoError) { LOGE("Unable to retrieve usage info file"); return false; } if (ksids != nullptr) ksids->clear(); if (provider_session_tokens != nullptr) provider_session_tokens->clear(); size_t num_records = file.usage_info().sessions_size(); for (size_t i = 0; i < num_records; ++i) { if ((ksids != nullptr) && !file.usage_info().sessions(i).key_set_id().empty()) { ksids->push_back(file.usage_info().sessions(i).key_set_id()); } if ((provider_session_tokens != nullptr) && !file.usage_info().sessions(i).token().empty()) { provider_session_tokens->push_back(file.usage_info().sessions(i).token()); } } return true; } bool DeviceFiles::GetProviderSessionToken(const std::string& app_id, const std::string& key_set_id, std::string* provider_session_token) { RETURN_FALSE_IF_UNINITIALIZED(); RETURN_FALSE_IF_NULL(provider_session_token); std::string file_name = GetUsageInfoFileName(app_id); if (!FileExists(file_name) || GetFileSize(file_name) == 0) { LOGE("Usage info file does not exists or is an empty file"); return false; } video_widevine_client::sdk::File file; if (RetrieveHashedFile(file_name, &file) != kNoError) { LOGE("Unable to retrieve usage info file"); return false; } size_t num_records = file.usage_info().sessions_size(); for (size_t i = 0; i < num_records; ++i) { if (file.usage_info().sessions(i).key_set_id() == key_set_id) { *provider_session_token = file.usage_info().sessions(i).token(); return true; } } return false; } bool DeviceFiles::DeleteUsageInfo(const std::string& usage_info_file_name, const std::string& provider_session_token) { RETURN_FALSE_IF_UNINITIALIZED(); video_widevine_client::sdk::File file; if (RetrieveHashedFile(usage_info_file_name, &file) != kNoError) { LOGE("Unable to retrieve usage info file"); return false; } UsageInfo* usage_info = file.mutable_usage_info(); int index = 0; bool found = false; for (; index < usage_info->sessions_size(); ++index) { if (usage_info->sessions(index).token() == provider_session_token) { found = true; break; } } if (!found) { LOGE("Unable to find provider session token: pst = %s", b2a_hex(provider_session_token).c_str()); return false; } google::protobuf::RepeatedPtrField* sessions = usage_info->mutable_sessions(); if (index < usage_info->sessions_size() - 1) { sessions->SwapElements(index, usage_info->sessions_size() - 1); } sessions->RemoveLast(); std::string serialized_file; file.SerializeToString(&serialized_file); return StoreFileWithHash(usage_info_file_name, serialized_file) == kNoError; } bool DeviceFiles::DeleteAllUsageInfoForApp( const std::string& usage_info_file_name, std::vector* provider_session_tokens) { RETURN_FALSE_IF_UNINITIALIZED(); RETURN_FALSE_IF_NULL(provider_session_tokens); provider_session_tokens->clear(); if (!FileExists(usage_info_file_name)) return true; video_widevine_client::sdk::File file; if (RetrieveHashedFile(usage_info_file_name, &file) == kNoError) { for (int i = 0; i < file.usage_info().sessions_size(); ++i) { provider_session_tokens->push_back(file.usage_info().sessions(i).token()); } } else { LOGW("Unable to retrieve usage info file"); } return RemoveFile(usage_info_file_name); } bool DeviceFiles::DeleteMultipleUsageInfoByKeySetIds( const std::string& usage_info_file_name, const std::vector& key_set_ids) { if (!FileExists(usage_info_file_name)) return false; if (key_set_ids.empty()) { LOGW("No key set IDs provided"); return true; } video_widevine_client::sdk::File file; if (RetrieveHashedFile(usage_info_file_name, &file) != kNoError) { LOGW("Unable to retrieve usage info file"); return false; } const auto is_deletable = [&key_set_ids]( const video_widevine_client::sdk::UsageInfo::ProviderSession& session) -> bool { return std::find(key_set_ids.cbegin(), key_set_ids.cend(), session.key_set_id()) != key_set_ids.cend(); }; auto sessions = file.mutable_usage_info()->mutable_sessions(); const int initial_size = sessions->size(); sessions->erase( std::remove_if(sessions->begin(), sessions->end(), is_deletable), sessions->end()); if (sessions->size() == initial_size) { // Nothing deleted. return true; } if (sessions->size() > 0) { std::string serialized_file; file.SerializeToString(&serialized_file); return StoreFileWithHash(usage_info_file_name, serialized_file) == kNoError; } return RemoveFile(usage_info_file_name); } bool DeviceFiles::DeleteAllUsageInfo() { RETURN_FALSE_IF_UNINITIALIZED(); return RemoveFile(kUsageInfoFileNamePrefix + std::string(kWildcard) + kUsageInfoFileNameExt); } bool DeviceFiles::RetrieveUsageInfo( const std::string& usage_info_file_name, std::vector >* usage_info) { RETURN_FALSE_IF_UNINITIALIZED(); RETURN_FALSE_IF_NULL(usage_info); if (!FileExists(usage_info_file_name) || GetFileSize(usage_info_file_name) == 0) { usage_info->resize(0); return true; } video_widevine_client::sdk::File file; if (RetrieveHashedFile(usage_info_file_name, &file) != kNoError) { LOGE("Unable to retrieve usage info file"); return false; } usage_info->resize(file.usage_info().sessions_size()); for (int i = 0; i < file.usage_info().sessions_size(); ++i) { (*usage_info)[i] = std::make_pair(file.usage_info().sessions(i).license_request(), file.usage_info().sessions(i).license()); } return true; } bool DeviceFiles::RetrieveUsageInfo(const std::string& usage_info_file_name, const std::string& provider_session_token, CdmKeyMessage* license_request, CdmKeyResponse* license, std::string* usage_entry, uint32_t* usage_entry_number) { RETURN_FALSE_IF_UNINITIALIZED(); video_widevine_client::sdk::File file; if (RetrieveHashedFile(usage_info_file_name, &file) != kNoError) { LOGE("Unable to retrieve usage info file"); return false; } int index = 0; for (; index < file.usage_info().sessions_size(); ++index) { if (file.usage_info().sessions(index).token() == provider_session_token) { *license_request = file.usage_info().sessions(index).license_request(); *license = file.usage_info().sessions(index).license(); *usage_entry = file.usage_info().sessions(index).usage_entry(); *usage_entry_number = file.usage_info().sessions(index).usage_entry_number(); return true; } } return false; } bool DeviceFiles::RetrieveUsageInfoByKeySetId( const std::string& usage_info_file_name, const std::string& key_set_id, std::string* provider_session_token, CdmKeyMessage* license_request, CdmKeyResponse* license, std::string* usage_entry, uint32_t* usage_entry_number) { RETURN_FALSE_IF_UNINITIALIZED(); video_widevine_client::sdk::File file; if (RetrieveHashedFile(usage_info_file_name, &file) != kNoError) { LOGE("Unable to retrieve usage info file"); return false; } int index = 0; for (; index < file.usage_info().sessions_size(); ++index) { if (file.usage_info().sessions(index).key_set_id() == key_set_id) { *provider_session_token = file.usage_info().sessions(index).token(); *license_request = file.usage_info().sessions(index).license_request(); *license = file.usage_info().sessions(index).license(); *usage_entry = file.usage_info().sessions(index).usage_entry(); *usage_entry_number = file.usage_info().sessions(index).usage_entry_number(); return true; } } return false; } bool DeviceFiles::StoreUsageInfo(const std::string& usage_info_file_name, const std::vector& usage_data) { RETURN_FALSE_IF_UNINITIALIZED(); video_widevine_client::sdk::File file; file.set_type(video_widevine_client::sdk::File::USAGE_INFO); file.set_version(video_widevine_client::sdk::File::VERSION_1); UsageInfo* usage_info = file.mutable_usage_info(); for (size_t i = 0; i < usage_data.size(); ++i) { UsageInfo_ProviderSession* provider_session = usage_info->add_sessions(); provider_session->set_token(usage_data[i].provider_session_token.data(), usage_data[i].provider_session_token.size()); provider_session->set_license_request(usage_data[i].license_request.data(), usage_data[i].license_request.size()); provider_session->set_license(usage_data[i].license.data(), usage_data[i].license.size()); provider_session->set_key_set_id(usage_data[i].key_set_id.data(), usage_data[i].key_set_id.size()); provider_session->set_usage_entry(usage_data[i].usage_entry); provider_session->set_usage_entry_number(usage_data[i].usage_entry_number); } std::string serialized_file; file.SerializeToString(&serialized_file); return StoreFileWithHash(usage_info_file_name, serialized_file) == kNoError; } bool DeviceFiles::UpdateUsageInfo(const std::string& usage_info_file_name, const std::string& provider_session_token, const CdmUsageData& usage_data) { RETURN_FALSE_IF_UNINITIALIZED(); video_widevine_client::sdk::File file; if (!FileExists(usage_info_file_name)) { LOGE("Usage info file does not exist"); return false; } if (RetrieveHashedFile(usage_info_file_name, &file) != kNoError) { LOGE("Unable to retrieve usage info file"); return false; } int index = 0; for (; index < file.usage_info().sessions_size(); ++index) { if (file.usage_info().sessions(index).token() == provider_session_token) { UsageInfo* usage_info = file.mutable_usage_info(); UsageInfo_ProviderSession* provider_session = usage_info->mutable_sessions(index); provider_session->set_license_request(usage_data.license_request); provider_session->set_license(usage_data.license); provider_session->set_key_set_id(usage_data.key_set_id); provider_session->set_usage_entry(usage_data.usage_entry); provider_session->set_usage_entry_number(usage_data.usage_entry_number); std::string serialized_file; file.SerializeToString(&serialized_file); return StoreFileWithHash(usage_info_file_name, serialized_file) == kNoError; } } return false; } bool DeviceFiles::RetrieveUsageInfo(const std::string& usage_info_file_name, std::vector* usage_data) { RETURN_FALSE_IF_UNINITIALIZED(); RETURN_FALSE_IF_NULL(usage_data); if (!FileExists(usage_info_file_name) || GetFileSize(usage_info_file_name) == 0) { usage_data->resize(0); return true; } video_widevine_client::sdk::File file; if (RetrieveHashedFile(usage_info_file_name, &file) != kNoError) { LOGE("Unable to retrieve usage info file"); return false; } usage_data->resize(file.usage_info().sessions_size()); for (int i = 0; i < file.usage_info().sessions_size(); ++i) { (*usage_data)[i].provider_session_token = file.usage_info().sessions(i).token(); (*usage_data)[i].license_request = file.usage_info().sessions(i).license_request(); (*usage_data)[i].license = file.usage_info().sessions(i).license(); (*usage_data)[i].key_set_id = file.usage_info().sessions(i).key_set_id(); (*usage_data)[i].usage_entry = file.usage_info().sessions(i).usage_entry(); (*usage_data)[i].usage_entry_number = file.usage_info().sessions(i).usage_entry_number(); } return true; } bool DeviceFiles::RetrieveUsageInfo(const std::string& usage_info_file_name, const std::string& provider_session_token, CdmUsageData* usage_data) { RETURN_FALSE_IF_UNINITIALIZED(); RETURN_FALSE_IF_NULL(usage_data); video_widevine_client::sdk::File file; if (RetrieveHashedFile(usage_info_file_name, &file) != kNoError) { LOGE("Unable to retrieve usage info file"); return false; } int index = 0; for (; index < file.usage_info().sessions_size(); ++index) { if (file.usage_info().sessions(index).token() == provider_session_token) { usage_data->provider_session_token = file.usage_info().sessions(index).token(); usage_data->license_request = file.usage_info().sessions(index).license_request(); usage_data->license = file.usage_info().sessions(index).license(); usage_data->key_set_id = file.usage_info().sessions(index).key_set_id(); usage_data->usage_entry = file.usage_info().sessions(index).usage_entry(); usage_data->usage_entry_number = file.usage_info().sessions(index).usage_entry_number(); return true; } } return false; } bool DeviceFiles::ListUsageInfoFiles( std::vector* usage_info_file_names) { RETURN_FALSE_IF_UNINITIALIZED(); RETURN_FALSE_IF_NULL(usage_info_file_names); // Get list of filenames std::vector filenames; if (!ListFiles(&filenames)) { return false; } // Scan list of all filenames and return only usage info filenames usage_info_file_names->clear(); for (size_t i = 0; i < filenames.size(); i++) { std::string* name = &filenames[i]; std::size_t pos_prefix = name->find(kUsageInfoFileNamePrefix); std::size_t pos_suffix = name->find(kUsageInfoFileNameExt); if (pos_prefix == std::string::npos || pos_suffix == std::string::npos) { // Skip this file - extension does not match continue; } usage_info_file_names->push_back(*name); } return true; } bool DeviceFiles::StoreHlsAttributes( const std::string& key_set_id, const CdmHlsMethod method, const std::vector& media_segment_iv) { RETURN_FALSE_IF_UNINITIALIZED(); // Fill in file information video_widevine_client::sdk::File file; file.set_type(video_widevine_client::sdk::File::HLS_ATTRIBUTES); file.set_version(video_widevine_client::sdk::File::VERSION_1); HlsAttributes* hls_attributes = file.mutable_hls_attributes(); switch (method) { case kHlsMethodAes128: hls_attributes->set_method(HlsAttributes_Method_AES_128); break; case kHlsMethodSampleAes: hls_attributes->set_method(HlsAttributes_Method_SAMPLE_AES); break; case kHlsMethodNone: default: LOGE("Unknown HLS method: %d", method); return false; break; } hls_attributes->set_media_segment_iv(&media_segment_iv[0], media_segment_iv.size()); std::string serialized_file; file.SerializeToString(&serialized_file); return StoreFileWithHash(key_set_id + kHlsAttributesFileNameExt, serialized_file) == kNoError; } bool DeviceFiles::RetrieveHlsAttributes( const std::string& key_set_id, CdmHlsMethod* method, std::vector* media_segment_iv) { RETURN_FALSE_IF_UNINITIALIZED(); video_widevine_client::sdk::File file; if (RetrieveHashedFile(key_set_id + kHlsAttributesFileNameExt, &file) != kNoError) { LOGE("Unable to retrieve key set HLS attributes file"); return false; } if (file.type() != video_widevine_client::sdk::File::HLS_ATTRIBUTES) { LOGE("Incorrect file type: type = %d", static_cast(file.type())); return false; } if (file.version() != video_widevine_client::sdk::File::VERSION_1) { LOGE("Incorrect file version: version = %d", static_cast(file.version())); return false; } if (!file.has_hls_attributes()) { LOGE("HLS attributes not present"); return false; } HlsAttributes attributes = file.hls_attributes(); switch (attributes.method()) { case HlsAttributes_Method_AES_128: *method = kHlsMethodAes128; break; case HlsAttributes_Method_SAMPLE_AES: *method = kHlsMethodSampleAes; break; default: LOGW("Unrecognized HLS method: %d", static_cast(attributes.method())); *method = kHlsMethodNone; break; } media_segment_iv->assign(attributes.media_segment_iv().begin(), attributes.media_segment_iv().end()); return true; } bool DeviceFiles::DeleteHlsAttributes(const std::string& key_set_id) { RETURN_FALSE_IF_UNINITIALIZED(); return RemoveFile(key_set_id + kHlsAttributesFileNameExt); } bool DeviceFiles::StoreUsageTableInfo( const CdmUsageTableHeader& usage_table_header, const std::vector& usage_entry_info) { RETURN_FALSE_IF_UNINITIALIZED(); // Fill in file information video_widevine_client::sdk::File file; file.set_type(video_widevine_client::sdk::File::USAGE_TABLE_INFO); file.set_version(video_widevine_client::sdk::File::VERSION_1); UsageTableInfo* usage_table_info = file.mutable_usage_table_info(); usage_table_info->set_usage_table_header(usage_table_header); for (size_t i = 0; i < usage_entry_info.size(); ++i) { UsageTableInfo_UsageEntryInfo* info = usage_table_info->add_usage_entry_info(); info->set_key_set_id(usage_entry_info[i].key_set_id); switch (usage_entry_info[i].storage_type) { case kStorageLicense: info->set_storage( UsageTableInfo_UsageEntryInfo_UsageEntryStorage_LICENSE); info->set_last_use_time(usage_entry_info[i].last_use_time); info->set_offline_license_expiry_time( usage_entry_info[i].offline_license_expiry_time); break; case kStorageUsageInfo: info->set_storage( UsageTableInfo_UsageEntryInfo_UsageEntryStorage_USAGE_INFO); info->set_usage_info_file_name( usage_entry_info[i].usage_info_file_name); info->set_last_use_time(usage_entry_info[i].last_use_time); break; case kStorageTypeUnknown: default: info->set_storage( UsageTableInfo_UsageEntryInfo_UsageEntryStorage_UNKNOWN); break; } } usage_table_info->set_use_lru(true); std::string serialized_file; file.SerializeToString(&serialized_file); return StoreFileWithHash(GetUsageTableFileName(), serialized_file) == kNoError; } bool DeviceFiles::RetrieveUsageTableInfo( CdmUsageTableHeader* usage_table_header, std::vector* usage_entry_info, bool* lru_upgrade) { RETURN_FALSE_IF_UNINITIALIZED(); RETURN_FALSE_IF_NULL(usage_table_header); RETURN_FALSE_IF_NULL(usage_entry_info); RETURN_FALSE_IF_NULL(lru_upgrade); video_widevine_client::sdk::File file; if (RetrieveHashedFile(GetUsageTableFileName(), &file) != kNoError) { LOGE("Unable to retrieve usage table file"); return false; } if (file.type() != video_widevine_client::sdk::File::USAGE_TABLE_INFO) { LOGE("Incorrect file type: type = %d, expected_type = %d", static_cast(file.type()), static_cast(video_widevine_client::sdk::File::USAGE_TABLE_INFO)); return false; } if (file.version() != video_widevine_client::sdk::File::VERSION_1) { LOGE("Incorrect file version: version = %d, expected_version = %d", static_cast(file.version()), static_cast(video_widevine_client::sdk::File::VERSION_1)); return false; } if (!file.has_usage_table_info()) { LOGE("Usage table info not present in file"); return false; } const UsageTableInfo& usage_table_info = file.usage_table_info(); *lru_upgrade = !usage_table_info.use_lru(); *usage_table_header = usage_table_info.usage_table_header(); usage_entry_info->resize(usage_table_info.usage_entry_info_size()); for (int i = 0; i < usage_table_info.usage_entry_info_size(); ++i) { const UsageTableInfo_UsageEntryInfo& info = usage_table_info.usage_entry_info(i); (*usage_entry_info)[i].key_set_id = info.key_set_id(); switch (info.storage()) { case UsageTableInfo_UsageEntryInfo_UsageEntryStorage_LICENSE: (*usage_entry_info)[i].storage_type = kStorageLicense; (*usage_entry_info)[i].last_use_time = info.last_use_time(); (*usage_entry_info)[i].offline_license_expiry_time = info.offline_license_expiry_time(); break; case UsageTableInfo_UsageEntryInfo_UsageEntryStorage_USAGE_INFO: (*usage_entry_info)[i].storage_type = kStorageUsageInfo; (*usage_entry_info)[i].usage_info_file_name = info.usage_info_file_name(); (*usage_entry_info)[i].last_use_time = info.last_use_time(); break; case UsageTableInfo_UsageEntryInfo_UsageEntryStorage_UNKNOWN: default: (*usage_entry_info)[i].storage_type = kStorageTypeUnknown; break; } } return true; } bool DeviceFiles::DeleteUsageTableInfo() { RETURN_FALSE_IF_UNINITIALIZED(); return RemoveFile(GetUsageTableFileName()); } DeviceFiles::ResponseType DeviceFiles::StoreFileWithHash( const std::string& name, const std::string& serialized_file) { std::string hash = Sha256Hash(serialized_file); // Fill in hashed file data HashedFile hash_file; hash_file.set_file(serialized_file); hash_file.set_hash(hash); std::string serialized_hash_file; hash_file.SerializeToString(&serialized_hash_file); return StoreFileRaw(name, serialized_hash_file); } DeviceFiles::ResponseType DeviceFiles::StoreFileRaw( const std::string& name, const std::string& serialized_file) { std::string path; if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { LOGE("Unable to get base path"); return kBasePathUnavailable; } path += name; auto file = file_system_->Open(path, FileSystem::kCreate | FileSystem::kTruncate); if (!file) { LOGE("Failed to open file: path = %s", path.c_str()); return kFileOpenFailed; } const ssize_t bytes_written = file->Write(serialized_file.data(), serialized_file.size()); if (bytes_written < 0) { LOGE("Failed to write to file: path = %s", path.c_str()); return kFileWriteError; } if (bytes_written != static_cast(serialized_file.size())) { LOGE( "Failed to fully write to file: path = %s, " "bytes_written = %zd, bytes_attempted = %zu", path.c_str(), bytes_written, serialized_file.size()); return kFileWriteError; } LOGV("Successfully stored raw file: path = %s, size = %zu", path.c_str(), serialized_file.size()); return kNoError; } DeviceFiles::ResponseType DeviceFiles::RetrieveHashedFile( const std::string& name, video_widevine_client::sdk::File* deserialized_file) { std::string serialized_file; if (deserialized_file == nullptr) { LOGE("File handle parameter |deserialized_file| not provided"); return kParameterNull; } std::string path; if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { LOGE("Unable to get base path"); return kBasePathUnavailable; } path += name; if (!file_system_->Exists(path)) { LOGE("File does not exist: path = %s", path.c_str()); return kFileNotFound; } const ssize_t file_size = file_system_->FileSize(path); if (file_size <= 0) { LOGE("File size is invalid: %s", path.c_str()); // Remove the corrupted file so the caller will not get the same error // when trying to access the file repeatedly, causing the system to stall. file_system_->Remove(path); return kInvalidFileSize; } auto file = file_system_->Open(path, FileSystem::kReadOnly); if (!file) { return kFileOpenFailed; } std::string serialized_hash_file; serialized_hash_file.resize(file_size); const ssize_t bytes_read = file->Read(&serialized_hash_file[0], serialized_hash_file.size()); if (bytes_read != file_size) { if (bytes_read < 0) { LOGE("Failed to read from file: path = %s", path.c_str()); } else { LOGE( "Failed to fully read from file: " "path = %s, bytes_read = %zd, bytes_attempted = %zd", path.c_str(), bytes_read, file_size); } // Remove the corrupted file so the caller will not get the same error // when trying to access the file repeatedly, causing the system to stall. file_system_->Remove(path); return kFileReadError; } LOGV("Successfully read file: path = %s, size = %zu", path.c_str(), serialized_hash_file.size()); HashedFile hash_file; if (!hash_file.ParseFromString(serialized_hash_file)) { LOGE("Unable to parse hash file"); // Remove the corrupted file so the caller will not get the same error // when trying to access the file repeatedly, causing the system to stall. file_system_->Remove(path); return kFileParseError1; } std::string hash = Sha256Hash(hash_file.file()); if (hash != hash_file.hash()) { LOGE("File hash mismatch: path = %s", path.c_str()); // Remove the corrupted file so the caller will not get the same error // when trying to access the file repeatedly, causing the system to stall. file_system_->Remove(path); return kFileHashMismatch; } if (!deserialized_file->ParseFromString(hash_file.file())) { LOGE("Unable to parse hashed file"); // Remove the corrupted file so the caller will not get the same error // when trying to access the file repeatedly, causing the system to stall. file_system_->Remove(path); return kFileParseError2; } return kNoError; } bool DeviceFiles::FileExists(const std::string& name) { std::string path; if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { LOGE("Unable to get base path"); return false; } path += name; return file_system_->Exists(path); } bool DeviceFiles::ListFiles(std::vector* names) { std::string path; if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { LOGE("Unable to get base path"); return false; } return file_system_->List(path, names); } bool DeviceFiles::RemoveFile(const std::string& name) { std::string path; if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { LOGE("Unable to get base path"); return false; } path += name; return file_system_->Remove(path); } ssize_t DeviceFiles::GetFileSize(const std::string& name) { std::string path; if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { LOGE("Unable to get base path"); return -1; } path += name; return file_system_->FileSize(path); } std::string DeviceFiles::GetCertificateFileName() { return kCertificateFileName; } std::string DeviceFiles::GetUsageTableFileName() { return kUsageTableFileName; } std::string DeviceFiles::GetHlsAttributesFileNameExtension() { return kHlsAttributesFileNameExt; } std::string DeviceFiles::GetLicenseFileNameExtension() { return kLicenseFileNameExt; } std::string DeviceFiles::GetUsageInfoFileName(const std::string& app_id) { std::string hash; if (app_id != "") { hash = GetFileNameSafeHash(app_id); } return kUsageInfoFileNamePrefix + hash + kUsageInfoFileNameExt; } std::string DeviceFiles::GetFileNameSafeHash(const std::string& input) { std::string hash = Md5Hash(input); return wvcdm::Base64SafeEncode( std::vector(hash.begin(), hash.end())); } } // namespace wvcdm