// Copyright 2013 Google Inc. All Rights Reserved. #include "device_files.h" #include #include #include "file_store.h" #include "log.h" #include "properties.h" #include "string_conversions.h" #include "wv_cdm_constants.h" #if defined(__APPLE__) #include #define SHA256 CC_SHA256 #define SHA256_DIGEST_LENGTH CC_SHA256_DIGEST_LENGTH #define MD5 CC_MD5 #define MD5_DIGEST_LENGTH CC_MD5_DIGEST_LENGTH #else #include #include #endif // 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; 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 kWildcard[] = "*"; bool Hash(const std::string& data, std::string* hash) { if (!hash) return false; hash->resize(SHA256_DIGEST_LENGTH); const unsigned char* input = reinterpret_cast(data.data()); unsigned char* output = reinterpret_cast(&(*hash)[0]); SHA256(input, data.size(), output); return true; } } // 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_) { LOGD("DeviceFiles::Init: Invalid FileSystem given."); return false; } std::string path; if (!Properties::GetDeviceFilesBasePath(security_level, &path)) { LOGW("DeviceFiles::Init: 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) { if (!initialized_) { LOGW("DeviceFiles::StoreCertificate: not initialized"); return false; } // 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); } bool DeviceFiles::RetrieveCertificate(std::string* certificate, std::string* wrapped_private_key) { if (!initialized_) { LOGW("DeviceFiles::RetrieveCertificate: not initialized"); return false; } video_widevine_client::sdk::File file; if (!RetrieveHashedFile(GetCertificateFileName(), &file)) { return false; } if (file.type() != video_widevine_client::sdk::File::DEVICE_CERTIFICATE) { LOGW("DeviceFiles::RetrieveCertificate: Incorrect file type"); return false; } if (file.version() != video_widevine_client::sdk::File::VERSION_1) { LOGW("DeviceFiles::RetrieveCertificate: Incorrect file version"); return false; } if (!file.has_device_certificate()) { LOGW("DeviceFiles::RetrieveCertificate: Certificate not present"); return false; } DeviceCertificate device_certificate = file.device_certificate(); *certificate = device_certificate.certificate(); *wrapped_private_key = device_certificate.wrapped_private_key(); return true; } bool DeviceFiles::HasCertificate() { if (!initialized_) { LOGW("DeviceFiles::HasCertificate: not initialized"); return false; } return FileExists(GetCertificateFileName()); } bool DeviceFiles::RemoveCertificate() { if (!initialized_) { LOGW("DeviceFiles::RemoveCertificate: not initialized"); return false; } return RemoveFile(GetCertificateFileName()); } bool DeviceFiles::StoreLicense( const std::string& key_set_id, const LicenseState state, const CdmInitData& pssh_data, const CdmKeyMessage& license_request, const CdmKeyResponse& license_message, const CdmKeyMessage& license_renewal_request, const CdmKeyResponse& license_renewal, const std::string& release_server_url, int64_t playback_start_time, int64_t last_playback_time, const CdmAppParameterMap& app_parameters) { if (!initialized_) { LOGW("DeviceFiles::StoreLicense: not initialized"); return false; } // 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 (state) { case kLicenseStateActive: license->set_state(License_LicenseState_ACTIVE); break; case kLicenseStateReleasing: license->set_state(License_LicenseState_RELEASING); break; default: LOGW("DeviceFiles::StoreLicense: Unknown license state: %u", state); return false; break; } license->set_pssh_data(pssh_data); license->set_license_request(license_request); license->set_license(license_message); license->set_renewal_request(license_renewal_request); license->set_renewal(license_renewal); license->set_release_server_url(release_server_url); license->set_playback_start_time(playback_start_time); license->set_last_playback_time(last_playback_time); NameValue* app_params; for (CdmAppParameterMap::const_iterator iter = app_parameters.begin(); iter != app_parameters.end(); ++iter) { app_params = license->add_app_parameters(); app_params->set_name(iter->first); app_params->set_value(iter->second); } std::string serialized_file; file.SerializeToString(&serialized_file); reserved_license_ids_.erase(key_set_id); return StoreFileWithHash(key_set_id + kLicenseFileNameExt, serialized_file); } bool DeviceFiles::RetrieveLicense( const std::string& key_set_id, LicenseState* state, CdmInitData* pssh_data, CdmKeyMessage* license_request, CdmKeyResponse* license_message, CdmKeyMessage* license_renewal_request, CdmKeyResponse* license_renewal, std::string* release_server_url, int64_t* playback_start_time, int64_t* last_playback_time, CdmAppParameterMap* app_parameters) { if (!initialized_) { LOGW("DeviceFiles::RetrieveLicense: not initialized"); return false; } video_widevine_client::sdk::File file; if (!RetrieveHashedFile(key_set_id + kLicenseFileNameExt, &file)) { return false; } if (file.type() != video_widevine_client::sdk::File::LICENSE) { LOGW("DeviceFiles::RetrieveLicense: Incorrect file type"); return false; } if (file.version() != video_widevine_client::sdk::File::VERSION_1) { LOGW("DeviceFiles::RetrieveLicense: Incorrect file version"); return false; } if (!file.has_license()) { LOGW("DeviceFiles::RetrieveLicense: License not present"); return false; } License license = file.license(); switch (license.state()) { case License_LicenseState_ACTIVE: *state = kLicenseStateActive; break; case License_LicenseState_RELEASING: *state = kLicenseStateReleasing; break; default: LOGW("DeviceFiles::RetrieveLicense: Unrecognized license state: %u", kLicenseStateUnknown); *state = kLicenseStateUnknown; break; } *pssh_data = license.pssh_data(); *license_request = license.license_request(); *license_message = license.license(); *license_renewal_request = license.renewal_request(); *license_renewal = license.renewal(); *release_server_url = license.release_server_url(); *playback_start_time = license.playback_start_time(); *last_playback_time = license.last_playback_time(); for (int i = 0; i < license.app_parameters_size(); ++i) { (*app_parameters)[license.app_parameters(i).name()] = license.app_parameters(i).value(); } return true; } bool DeviceFiles::DeleteLicense(const std::string& key_set_id) { if (!initialized_) { LOGW("DeviceFiles::DeleteLicense: not initialized"); return false; } return RemoveFile(key_set_id + kLicenseFileNameExt); } bool DeviceFiles::DeleteAllLicenses() { if (!initialized_) { LOGW("DeviceFiles::DeleteAllLicenses: not initialized"); return false; } return RemoveFile(std::string(kWildcard) + kLicenseFileNameExt); } bool DeviceFiles::DeleteAllFiles() { if (!initialized_) { LOGW("DeviceFiles::DeleteAllFiles: not initialized"); return false; } // 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) { if (!initialized_) { LOGW("DeviceFiles::LicenseExists: not initialized"); return false; } return reserved_license_ids_.count(key_set_id) || FileExists(key_set_id + kLicenseFileNameExt); } bool DeviceFiles::ReserveLicenseId(const std::string& key_set_id) { if (!initialized_) { LOGW("DeviceFiles::ReserveLicenseId: not initialized"); return false; } reserved_license_ids_.insert(key_set_id); return true; } bool DeviceFiles::UnreserveLicenseId(const std::string& key_set_id) { if (!initialized_) { LOGW("DeviceFiles::UnreserveLicenseId: not initialized"); return false; } 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& app_id, const std::string& key_set_id) { if (!initialized_) { LOGW("DeviceFiles::StoreUsageInfo: not initialized"); return false; } video_widevine_client::sdk::File file; std::string file_name = GetUsageInfoFileName(app_id); if (!FileExists(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(file_name, &file)) { LOGW("DeviceFiles::StoreUsageInfo: Unable to parse 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()); std::string serialized_file; file.SerializeToString(&serialized_file); return StoreFileWithHash(file_name, serialized_file); } bool DeviceFiles::DeleteUsageInfo(const std::string& app_id, const std::string& provider_session_token) { if (!initialized_) { LOGW("DeviceFiles::DeleteUsageInfo: not initialized"); return false; } video_widevine_client::sdk::File file; std::string file_name = GetUsageInfoFileName(app_id); if (!RetrieveHashedFile(file_name, &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) { LOGW( "DeviceFiles::DeleteUsageInfo: Unable to find provider session " "token: %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(file_name, serialized_file); } bool DeviceFiles::DeleteAllUsageInfoForApp( const std::string& app_id, std::vector* provider_session_tokens) { if (!initialized_) { LOGW("DeviceFiles::DeleteAllUsageInfoForApp: not initialized"); return false; } if (NULL == provider_session_tokens) { LOGW("DeviceFiles::DeleteAllUsageInfoForApp: pst destination not provided"); return false; } provider_session_tokens->clear(); std::string file_name = GetUsageInfoFileName(app_id); if (!FileExists(file_name)) return true; video_widevine_client::sdk::File file; if (RetrieveHashedFile(file_name, &file)) { for (int i = 0; i < file.usage_info().sessions_size(); ++i) { provider_session_tokens->push_back(file.usage_info().sessions(i).token()); } } else { LOGW("DeviceFiles::DeleteAllUsageInfoForApp: Unable to retrieve file"); } return RemoveFile(file_name); } bool DeviceFiles::RetrieveUsageInfo( const std::string& app_id, std::vector >* usage_info) { if (!initialized_) { LOGW("DeviceFiles::RetrieveUsageInfo: not initialized"); return false; } if (NULL == usage_info) { LOGW( "DeviceFiles::RetrieveUsageInfo: license destination not " "provided"); return false; } std::string file_name = GetUsageInfoFileName(app_id); if (!FileExists(file_name) || GetFileSize(file_name) == 0) { usage_info->resize(0); return true; } video_widevine_client::sdk::File file; if (!RetrieveHashedFile(file_name, &file)) { LOGW("DeviceFiles::RetrieveUsageInfo: Unable to parse 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& app_id, const std::string& provider_session_token, CdmKeyMessage* license_request, CdmKeyResponse* license_response) { if (!initialized_) { LOGW("DeviceFiles::RetrieveUsageInfo: not initialized"); return false; } std::string file_name = GetUsageInfoFileName(app_id); video_widevine_client::sdk::File file; if (!RetrieveHashedFile(file_name, &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_response = file.usage_info().sessions(index).license(); return true; } } return false; } bool DeviceFiles::RetrieveUsageInfoByKeySetId( const std::string& app_id, const std::string& key_set_id, CdmKeyMessage* license_request, CdmKeyResponse* license_response) { if (!initialized_) { LOGW("DeviceFiles::RetrieveUsageInfoByKeySetId: not initialized"); return false; } std::string file_name = GetUsageInfoFileName(app_id); video_widevine_client::sdk::File file; if (!RetrieveHashedFile(file_name, &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) { *license_request = file.usage_info().sessions(index).license_request(); *license_response = file.usage_info().sessions(index).license(); return true; } } return false; } bool DeviceFiles::StoreHlsAttributes( const std::string& key_set_id, const CdmHlsMethod method, const std::vector& media_segment_iv) { if (!initialized_) { LOGW("DeviceFiles::StoreHlsAttributes: not initialized"); return false; } // 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: LOGW("DeviceFiles::StoreHlsAttributeInfo: Unknown HLS method: %u", 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); } bool DeviceFiles::RetrieveHlsAttributes( const std::string& key_set_id, CdmHlsMethod* method, std::vector* media_segment_iv) { if (!initialized_) { LOGW("DeviceFiles::RetrieveHlsAttributes: not initialized"); return false; } video_widevine_client::sdk::File file; if (!RetrieveHashedFile(key_set_id + kHlsAttributesFileNameExt, &file)) { return false; } if (file.type() != video_widevine_client::sdk::File::HLS_ATTRIBUTES) { LOGW("DeviceFiles::RetrieveHlsAttributes: Incorrect file type: %u", file.type()); return false; } if (file.version() != video_widevine_client::sdk::File::VERSION_1) { LOGW("DeviceFiles::RetrieveHlsAttributes: Incorrect file version: %u", file.version()); return false; } if (!file.has_hls_attributes()) { LOGW("DeviceFiles::RetrieveHlsAttributes: 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("DeviceFiles::RetrieveHlsAttributes: Unrecognized HLS method: %u", 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) { if (!initialized_) { LOGW("DeviceFiles::DeleteHlsAttributes: not initialized"); return false; } return RemoveFile(key_set_id + kHlsAttributesFileNameExt); } bool DeviceFiles::StoreFileWithHash(const std::string& name, const std::string& serialized_file) { // calculate SHA hash std::string hash; if (!Hash(serialized_file, &hash)) { LOGW("DeviceFiles::StoreFileWithHash: Hash computation failed"); return false; } // 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); } bool DeviceFiles::StoreFileRaw(const std::string& name, const std::string& serialized_file) { std::string path; if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { LOGW("DeviceFiles::StoreFileRaw: Unable to get base path"); return false; } path += name; File* file = file_system_->Open(path, FileSystem::kCreate | FileSystem::kTruncate); if (!file) { LOGW("DeviceFiles::StoreFileRaw: File open failed: %s", path.c_str()); return false; } ssize_t bytes = file->Write(serialized_file.data(), serialized_file.size()); file->Close(); if (bytes != static_cast(serialized_file.size())) { LOGW( "DeviceFiles::StoreFileRaw: write failed: (actual: %d, " "expected: %d)", bytes, serialized_file.size()); return false; } LOGV("DeviceFiles::StoreFileRaw: success: %s (%db)", path.c_str(), serialized_file.size()); return true; } bool DeviceFiles::RetrieveHashedFile( const std::string& name, video_widevine_client::sdk::File* deserialized_file) { std::string serialized_file; if (!deserialized_file) { LOGW("DeviceFiles::RetrieveHashedFile: Unspecified file parameter"); return false; } std::string path; if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { LOGW("DeviceFiles::StoreFileWithHash: Unable to get base path"); return false; } path += name; if (!file_system_->Exists(path)) { LOGW("DeviceFiles::RetrieveHashedFile: %s does not exist", path.c_str()); return false; } ssize_t bytes = file_system_->FileSize(path); if (bytes <= 0) { LOGW("DeviceFiles::RetrieveHashedFile: File size 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 false; } File* file = file_system_->Open(path, FileSystem::kReadOnly); if (!file) { return false; } std::string serialized_hash_file; serialized_hash_file.resize(bytes); bytes = file->Read(&serialized_hash_file[0], serialized_hash_file.size()); file->Close(); if (bytes != static_cast(serialized_hash_file.size())) { LOGW("DeviceFiles::RetrieveHashedFile: read failed"); return false; } LOGV("DeviceFiles::RetrieveHashedFile: success: %s (%db)", path.c_str(), serialized_hash_file.size()); HashedFile hash_file; if (!hash_file.ParseFromString(serialized_hash_file)) { LOGW("DeviceFiles::RetrieveHashedFile: Unable to parse hash file"); return false; } std::string hash; if (!Hash(hash_file.file(), &hash)) { LOGW("DeviceFiles::RetrieveHashedFile: Hash computation failed"); return false; } if (hash != hash_file.hash()) { LOGW("DeviceFiles::RetrieveHashedFile: Hash mismatch"); // 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 false; } if (!deserialized_file->ParseFromString(hash_file.file())) { LOGW("DeviceFiles::RetrieveHashedFile: Unable to parse file"); return false; } return true; } bool DeviceFiles::FileExists(const std::string& name) { std::string path; if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { LOGW("DeviceFiles::FileExists: Unable to get base path"); return false; } path += name; return file_system_->Exists(path); } bool DeviceFiles::RemoveFile(const std::string& name) { std::string path; if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { LOGW("DeviceFiles::RemoveFile: 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)) { LOGW("DeviceFiles::GetFileSize: Unable to get base path"); return -1; } path += name; return file_system_->FileSize(path); } std::string DeviceFiles::GetCertificateFileName() { return kCertificateFileName; } 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::vector hash(MD5_DIGEST_LENGTH); const unsigned char* input_ptr = reinterpret_cast(input.data()); MD5(input_ptr, input.size(), &hash[0]); return wvcdm::Base64SafeEncode(hash); } } // namespace wvcdm