Files
android/libwvdrmengine/cdm/core/src/device_files.cpp
Alex Dale 2c05c65138 Delete secure stops by key set ID.
[ Merge of http://go/wvgerrit/165617 ]

Similar to the issue with updating secure stops by PST (see
http://go/wvgerrit/165597), when deleting different secure stops with
the same PST results in unintended behavior.  This CL changes how the
CDM identifies which secure stop to delete from storaged based on the
key set ID rather than the PST.

Bug: 263316107
Test: device_files_unittest
Test: GTS MediaDrmParameterizedTests and MediaDrmStressTest
Change-Id: Ic3843a1435f252f052c7189423c211c28ed74eaa
2023-02-07 22:32:24 -08:00

2164 lines
75 KiB
C++

// 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 "device_files.h"
#include <inttypes.h>
#include <string.h>
#include <algorithm>
#include <string>
#include "cdm_random.h"
#include "certificate_provisioning.h"
#include "clock.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::OemCertificate;
using video_widevine_client::sdk::UsageInfo;
using video_widevine_client::sdk::UsageInfo_DrmUsageCertificate;
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_CERTIFICATE_STATE_CANNOT_HANDLE_IF_NULL(PARAM) \
if ((PARAM) == nullptr) { \
LOGE("Output parameter |" STRINGIFY(PARAM) "| not provided"); \
return DeviceFiles::kCannotHandle; \
}
#define RETURN_CERTIFICATE_STATE_CANNOT_HANDLE_IF_UNINITIALIZED() \
if (!initialized_) { \
LOGE("Device files is not initialized"); \
return DeviceFiles::kCannotHandle; \
}
#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 wvcdm {
using UniqueLock = std::unique_lock<std::mutex>;
namespace {
const char kEmptyFileName[] = "";
const char kFalse[] = "false";
const char kHlsAttributesFileNameExt[] = ".hal";
const char kLicenseFileNameExt[] = ".lic";
const char kTrue[] = "true";
const char kUsageInfoFileNameExt[] = ".bin";
const char kUsageInfoFileNamePrefix[] = "usage";
const char kUsageTableFileName[] = "usgtable.bin";
const char kOkpInfoFileName[] = "okp.bin";
const char kWildcard[] = "*";
// TODO(b/192430982): Renable expiration of legacy DRM certificates
// constexpr int64_t kFourMonthsInSeconds = (2 * 30 + 2 * 31) * 24 * 60 * 60;
// Helper methods
bool SetDeviceCertificate(const std::string& certificate,
const CryptoWrappedKey& private_key,
DeviceCertificate* mutable_device_certificate) {
RETURN_FALSE_IF_NULL(mutable_device_certificate);
mutable_device_certificate->set_certificate(certificate);
mutable_device_certificate->set_wrapped_private_key(private_key.key());
switch (private_key.type()) {
case CryptoWrappedKey::kRsa:
mutable_device_certificate->set_key_type(DeviceCertificate::RSA);
return true;
case CryptoWrappedKey::kEcc:
mutable_device_certificate->set_key_type(DeviceCertificate::ECC);
return true;
case CryptoWrappedKey::kUninitialized: // Suppress compiler
// warnings.
default:
LOGE("Unexpected key type: %d", private_key.type());
return false;
}
}
bool ExtractFromDeviceCertificate(const DeviceCertificate& device_certificate,
std::string* certificate,
CryptoWrappedKey* private_key) {
RETURN_FALSE_IF_NULL(certificate);
RETURN_FALSE_IF_NULL(private_key);
bool has_certificate = device_certificate.has_certificate();
bool has_key = device_certificate.has_wrapped_private_key();
// If no certificate information, nothing to be done. DeviceCertificate
// is a legacy DRM certificate
if (!has_certificate && !has_key) return true;
// Flag if not a default certificate
if (!has_certificate || !has_key) {
LOGE(
"Device certificate proto belongs to neither a default or legacy cert. "
"has_certificate: %s, has_key: %s",
has_certificate ? kTrue : kFalse, has_key ? kTrue : kFalse);
return false;
}
if (device_certificate.certificate().empty() ||
device_certificate.wrapped_private_key().empty()) {
LOGE(
"Device certificate proto belongs does not have a valid certificate or "
"wrapped key. certificate size: %zu, wrapped key size: %zu",
device_certificate.certificate().size(),
device_certificate.wrapped_private_key().size());
return false;
}
*certificate = device_certificate.certificate();
private_key->Clear();
private_key->set_key(device_certificate.wrapped_private_key());
if (device_certificate.has_key_type()) {
const DeviceCertificate::PrivateKeyType key_type =
device_certificate.key_type();
switch (key_type) {
case DeviceCertificate::RSA:
private_key->set_type(CryptoWrappedKey::kRsa);
break;
case DeviceCertificate::ECC:
private_key->set_type(CryptoWrappedKey::kEcc);
break;
default:
LOGW("Unknown DRM key type, defaulting to RSA: type = %d", key_type);
private_key->set_type(CryptoWrappedKey::kRsa);
break;
}
} else {
// Possible that device certificate is from V15, in this case, the
// only supported key of at that time was RSA.
LOGD("No key type info, assuming RSA");
private_key->set_type(CryptoWrappedKey::kRsa);
}
return true;
}
bool FindOrInsertUsageCertificate(const std::string& drm_certificate,
const CryptoWrappedKey& wrapped_private_key,
UsageInfo* usage_info,
uint32_t* drm_certificate_id) {
RETURN_FALSE_IF_NULL(usage_info);
RETURN_FALSE_IF_NULL(drm_certificate_id);
// Scan |drm_certificate_cache| for |drm_certificate|. If present,
// return the id
std::set<uint32_t> ids;
for (const UsageInfo_DrmUsageCertificate& drm_device_cert :
usage_info->drm_certificate_cache()) {
if (drm_device_cert.drm_certificate().certificate() == drm_certificate) {
*drm_certificate_id = drm_device_cert.drm_certificate_id();
return true;
}
ids.insert(drm_device_cert.drm_certificate_id());
}
uint32_t last_id = 0;
// |drm_certificate| is not in the cache. Find the first non-contiguous
// id number to insert
for (uint32_t id : ids) {
if (id > last_id + 1) {
break;
}
last_id = id;
}
if (ids.empty())
*drm_certificate_id = 0;
else
*drm_certificate_id = last_id + 1;
// Now insert into |drm_certificate_cache|
UsageInfo_DrmUsageCertificate* drm_usage_certificate =
usage_info->add_drm_certificate_cache();
drm_usage_certificate->set_drm_certificate_id(*drm_certificate_id);
return SetDeviceCertificate(drm_certificate, wrapped_private_key,
drm_usage_certificate->mutable_drm_certificate());
}
bool FindUsageCertificate(
uint32_t drm_certificate_id,
const google::protobuf::RepeatedPtrField<UsageInfo_DrmUsageCertificate>&
drm_certificate_cache,
std::string* drm_certificate, CryptoWrappedKey* wrapped_private_key) {
for (const UsageInfo_DrmUsageCertificate& drm_usage_cert :
drm_certificate_cache) {
if (drm_usage_cert.drm_certificate_id() == drm_certificate_id) {
return ExtractFromDeviceCertificate(drm_usage_cert.drm_certificate(),
drm_certificate, wrapped_private_key);
}
}
LOGE("Unable to find any certificate in usage cache for entry: %d",
drm_certificate_id);
return false;
}
bool UsageCertificateCacheCleanUp(UsageInfo* usage_info) {
const google::protobuf::RepeatedPtrField<UsageInfo_ProviderSession>&
provider_sessions = usage_info->sessions();
google::protobuf::RepeatedPtrField<UsageInfo_DrmUsageCertificate>*
drm_certificate_cache = usage_info->mutable_drm_certificate_cache();
// Find all the DRM certificate ids in |drm_certificate_cache|
std::set<uint32_t> ids;
for (const UsageInfo_DrmUsageCertificate& drm_usage_cert :
*drm_certificate_cache) {
ids.insert(drm_usage_cert.drm_certificate_id());
}
// Next find all the DRM certificate ids in |provider_sessions|
std::set<uint32_t> session_ids;
for (const UsageInfo_ProviderSession& session : provider_sessions) {
session_ids.insert(session.drm_certificate_id());
}
// Now find all the entry numbers for DRM certificates in
// |drm_device_certificates| but not in |provider_sessions|. These need to
// be removed.
std::set<uint32_t> ids_to_erase;
std::set_difference(ids.begin(), ids.end(), session_ids.begin(),
session_ids.end(),
std::inserter(ids_to_erase, ids_to_erase.begin()));
const auto is_deletable =
[&ids_to_erase](
const UsageInfo_DrmUsageCertificate& usage_certificate) -> bool {
return std::find(ids_to_erase.cbegin(), ids_to_erase.cend(),
usage_certificate.drm_certificate_id()) !=
ids_to_erase.cend();
};
drm_certificate_cache->erase(
std::remove_if(drm_certificate_cache->begin(),
drm_certificate_cache->end(), is_deletable),
drm_certificate_cache->end());
return true;
}
} // namespace
// static
const char* DeviceFiles::CertificateStateToString(CertificateState state) {
switch (state) {
case kCertificateValid:
return "Valid";
case kCertificateExpired:
return "Expired";
case kCertificateNotFound:
return "NotFound";
case kCertificateInvalid:
return "Invalid";
case kCannotHandle:
return "CannotHandle";
}
return UnknownEnumValueToString(static_cast<int>(state));
}
// static
const char* DeviceFiles::CertificateTypeToString(CertificateType type) {
switch (type) {
case kCertificateDefault:
return "Default";
case kCertificateLegacy:
return "Legacy";
case kCertificateAtsc:
return "ATSC";
}
return UnknownEnumValueToString(static_cast<int>(type));
}
// static
const char* DeviceFiles::ResponseTypeToString(ResponseType type) {
switch (type) {
case kNoError:
return "NoError";
case kObjectNotInitialized:
return "ObjectNotInitialized";
case kParameterNull:
return "ParameterNull";
case kBasePathUnavailable:
return "PathUnavailable";
case kFileNotFound:
return "NotFound";
case kFileOpenFailed:
return "OpenFailed";
case kFileWriteError:
return "WriteError";
case kFileReadError:
return "ReadError";
case kInvalidFileSize:
return "InvalidFileSize";
case kHashComputationFailed:
return "HashFailed";
case kFileHashMismatch:
return "HashMismatch";
case kFileParseError1:
return "ParseHashedFileError";
case kFileParseError2:
return "ParseFileError";
case kUnknownLicenseState:
return "UnknownLicenseState";
case kIncorrectFileType:
return "IncorrectFileType";
case kIncorrectFileVersion:
return "IncorrectFileVersion";
case kLicenseNotPresent:
return "LicenseNotFound";
case kResponseTypeBase: // Not a valid value.
break;
}
return UnknownEnumValueToString(static_cast<int>(type));
}
// static
std::set<std::string> DeviceFiles::reserved_license_ids_;
std::mutex DeviceFiles::reserved_license_ids_mutex_;
DeviceFiles::DeviceFiles(wvutil::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 wvutil::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 CryptoWrappedKey& private_key) {
RETURN_FALSE_IF_UNINITIALIZED();
if (certificate.empty()) {
LOGE("Missing certificate information");
return false;
}
if (!private_key.IsValid()) {
LOGE("Private key is invalid");
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();
int64_t creation_time_seconds;
int64_t expiration_time_seconds;
uint32_t system_id;
if (!CertificateProvisioning::ExtractDeviceInfo(
certificate, nullptr, &system_id, &creation_time_seconds,
&expiration_time_seconds))
return false;
if (creation_time_seconds <= 0) {
LOGE("Invalid certificate creation time %" PRId64, creation_time_seconds);
return false;
}
const bool default_certificate = expiration_time_seconds >= 0;
if (!SetDeviceCertificate(certificate, private_key, device_certificate))
return false;
if (default_certificate) {
wvutil::Clock clock;
device_certificate->set_acquisition_time_seconds(clock.GetCurrentTime());
}
/* TODO(b/192430982): Renable expiration of legacy DRM certificates
else {
// Since certificates of type kCertificateAtsc are not allowed to be
// stored, this is a certificate of type kCertificateLegacy.
// The only time when a legacy certificate is stored is when it does not
// have an expiration time. Set expiration time to 6 months +- 2 months.
wvutil::Clock clock;
const int64_t current_time = clock.GetCurrentTime();
wvutil::CdmRandomGenerator rng(current_time & 0xffffffff);
device_certificate->set_expiration_time_seconds(
current_time + kFourMonthsInSeconds +
rng.RandomInRange(kFourMonthsInSeconds));
}
*/
std::string serialized_file;
file.SerializeToString(&serialized_file);
std::string certificate_file_name;
const CertificateType certificate_type =
default_certificate ? kCertificateDefault : kCertificateLegacy;
if (!GetCertificateFileName(certificate_type, &certificate_file_name)) {
LOGE("Unable to get certificate file name of type: %d", certificate_type);
return false;
}
return StoreFileWithHash(certificate_file_name, serialized_file) == kNoError;
}
DeviceFiles::CertificateState DeviceFiles::RetrieveCertificate(
bool atsc_mode_enabled, std::string* certificate,
CryptoWrappedKey* private_key, std::string* serial_number,
uint32_t* system_id) {
RETURN_CERTIFICATE_STATE_CANNOT_HANDLE_IF_UNINITIALIZED();
RETURN_CERTIFICATE_STATE_CANNOT_HANDLE_IF_NULL(certificate);
RETURN_CERTIFICATE_STATE_CANNOT_HANDLE_IF_NULL(private_key);
if (!HasCertificate(atsc_mode_enabled)) {
LOGW("Unable to find certificate, atsc mode: %s",
atsc_mode_enabled ? "enabled" : "disabled");
return kCertificateNotFound;
}
if (atsc_mode_enabled)
return RetrieveCertificate(kCertificateAtsc, certificate, private_key,
serial_number, system_id);
if (HasCertificate(kCertificateDefault))
return RetrieveCertificate(kCertificateDefault, certificate, private_key,
serial_number, system_id);
return RetrieveCertificate(kCertificateLegacy, certificate, private_key,
serial_number, system_id);
}
DeviceFiles::CertificateState DeviceFiles::RetrieveCertificate(
CertificateType certificate_type, std::string* certificate,
CryptoWrappedKey* wrapped_private_key, std::string* serial_number,
uint32_t* system_id) {
RETURN_CERTIFICATE_STATE_CANNOT_HANDLE_IF_NULL(certificate);
RETURN_CERTIFICATE_STATE_CANNOT_HANDLE_IF_NULL(wrapped_private_key);
std::string certificate_file_name;
if (!GetCertificateFileName(certificate_type, &certificate_file_name)) {
LOGW("Unable to find certificate file name for type: %d", certificate_type);
return kCannotHandle;
}
video_widevine_client::sdk::File file;
if (RetrieveHashedFile(certificate_file_name, &file) != kNoError) {
LOGW("Unable to retrieve certificate file");
return kCertificateNotFound;
}
if (file.type() != video_widevine_client::sdk::File::DEVICE_CERTIFICATE) {
LOGE("Certificate file is of incorrect file type: type = %d",
static_cast<int>(file.type()));
return kCertificateInvalid;
}
if (file.version() != video_widevine_client::sdk::File::VERSION_1) {
LOGE("Certificate file is of incorrect file version: version = %d",
static_cast<int>(file.version()));
return kCertificateInvalid;
}
if (!file.has_device_certificate()) {
LOGE("Certificate not present");
return kCertificateInvalid;
}
const DeviceCertificate& device_certificate = file.device_certificate();
if (!ExtractFromDeviceCertificate(device_certificate, certificate,
wrapped_private_key)) {
LOGE("Unable to extract from device certificate");
return kCertificateInvalid;
}
int64_t creation_time_seconds;
int64_t expiration_time_seconds;
if (!CertificateProvisioning::ExtractDeviceInfo(
device_certificate.certificate(), serial_number, system_id,
&creation_time_seconds, &expiration_time_seconds))
return kCertificateInvalid;
wvutil::Clock clock;
const int64_t current_time = clock.GetCurrentTime();
switch (certificate_type) {
case kCertificateDefault: {
// Validation check for DRM certificate that includes an expiration
// time set by the provisioning service. Since provisioning and
// client clocks may not be in sync, verify by comparing time
// elapsed since the certificate was acquired with the expiration
// period specified by the service.
// First verify that all the fields are set to valid values.
// The service will validate certificate expiration so tampering of
// time values at the client is not a concern.
if (creation_time_seconds <= 0) {
LOGE("Invalid creation time of default certificate: %" PRId64,
creation_time_seconds);
return kCertificateInvalid;
}
if (expiration_time_seconds < 0) {
LOGE("Invalid expiration time of default certificate: %" PRId64,
expiration_time_seconds);
return kCertificateInvalid;
}
if (expiration_time_seconds == UNLIMITED_DURATION)
return kCertificateValid;
if (!device_certificate.has_acquisition_time_seconds()) {
LOGE("Acquisition time of default certificate not available");
return kCertificateInvalid;
}
const int64_t acquisition_time_seconds =
device_certificate.acquisition_time_seconds();
if (acquisition_time_seconds <= 0) {
LOGE("Invalid acquisition time of default certificate: %" PRId64,
acquisition_time_seconds);
return kCertificateInvalid;
}
if (current_time < acquisition_time_seconds) {
LOGE("Time not valid: current time: %" PRId64
", acquisition time: %" PRId64,
current_time, acquisition_time_seconds);
return kCannotHandle;
}
if (expiration_time_seconds < creation_time_seconds) {
LOGE("Time not valid: expiration time: %" PRId64
", creation time: %" PRId64,
expiration_time_seconds, creation_time_seconds);
return kCertificateInvalid;
}
// |current_time| and |acquisition_time_seconds| are client clock
// times while |expiration_time_seconds| and |creation_time_seconds|
// are times specified by the provisioning service
if (current_time - acquisition_time_seconds >
expiration_time_seconds - creation_time_seconds) {
return kCertificateExpired;
}
return kCertificateValid;
}
case kCertificateLegacy: {
/* TODO(b/192430982): Renable expiration of legacy DRM certificates
// Validation check for DRM certificate without an expiration
// time set by the provisioning service. Add an expiry time
// within the next 6 months +/- 2 months, if one has not been set.
if (!device_certificate.has_expiration_time_seconds()) {
StoreCertificate(*certificate, *wrapped_private_key);
return kCertificateValid;
}
const int64_t expiration_time_seconds =
device_certificate.expiration_time_seconds();
if (expiration_time_seconds <= 0) {
LOGE("Invalid expiration time of legacy certificate: %" PRId64,
expiration_time_seconds);
return kCertificateInvalid;
}
if (current_time > expiration_time_seconds) return kCertificateExpired;
*/
return kCertificateValid;
}
case kCertificateAtsc:
// No expiration enforced
return kCertificateValid;
default:
// Should never happen. This should be detected earlier when fetching
// the file name
LOGE("Invalid certificate type: %d", certificate_type);
return kCertificateInvalid;
}
}
bool DeviceFiles::RetrieveLegacyCertificate(std::string* certificate,
CryptoWrappedKey* private_key,
std::string* serial_number,
uint32_t* system_id) {
RETURN_FALSE_IF_UNINITIALIZED();
RETURN_FALSE_IF_NULL(certificate);
RETURN_FALSE_IF_NULL(private_key);
if (!HasCertificate(kCertificateLegacy)) return false;
const CertificateState state = RetrieveCertificate(
kCertificateLegacy, certificate, private_key, serial_number, system_id);
if (state == kCertificateValid || state == kCertificateExpired) return true;
return false;
}
bool DeviceFiles::HasCertificate(bool atsc_mode_enabled) {
RETURN_FALSE_IF_UNINITIALIZED();
if (atsc_mode_enabled) return HasCertificate(kCertificateAtsc);
return HasCertificate(kCertificateDefault) ||
HasCertificate(kCertificateLegacy);
}
bool DeviceFiles::RemoveCertificate() {
RETURN_FALSE_IF_UNINITIALIZED()
std::string certificate_file_name;
if (GetCertificateFileName(kCertificateLegacy, &certificate_file_name))
RemoveFile(certificate_file_name);
if (GetCertificateFileName(kCertificateDefault, &certificate_file_name))
return RemoveFile(certificate_file_name);
return true;
}
bool DeviceFiles::RemoveOemCertificate() {
RETURN_FALSE_IF_UNINITIALIZED()
std::string certificate_file_name;
if (GetOemCertificateFileName(&certificate_file_name)) {
return RemoveFile(certificate_file_name);
}
return true;
}
bool DeviceFiles::StoreOemCertificate(const std::string& certificate,
const CryptoWrappedKey& private_key) {
RETURN_FALSE_IF_UNINITIALIZED();
if (certificate.empty()) {
LOGE("Missing certificate information");
return false;
}
if (!private_key.IsValid()) {
LOGE("Private key is invalid");
return false;
}
std::string certificate_file_name;
if (!GetOemCertificateFileName(&certificate_file_name)) {
LOGE("Unable to get certificate file name");
return false;
}
// Fill in file information
video_widevine_client::sdk::File file;
file.set_type(video_widevine_client::sdk::File::OEM_CERTIFICATE);
file.set_version(video_widevine_client::sdk::File::VERSION_1);
OemCertificate* oem_certificate = file.mutable_oem_certificate();
oem_certificate->set_certificate(certificate);
oem_certificate->set_wrapped_private_key(private_key.key());
switch (private_key.type()) {
case wvcdm::CryptoWrappedKey::kRsa:
oem_certificate->set_key_type(OemCertificate::RSA);
break;
case wvcdm::CryptoWrappedKey::kEcc:
oem_certificate->set_key_type(OemCertificate::ECC);
break;
case wvcdm::CryptoWrappedKey::kUninitialized:
default:
LOGE("Unexpected key type: %d", private_key.type());
return false;
}
std::string serialized_file;
file.SerializeToString(&serialized_file);
return StoreFileWithHash(certificate_file_name, serialized_file) == kNoError;
}
DeviceFiles::CertificateState DeviceFiles::RetrieveOemCertificate(
std::string* certificate, CryptoWrappedKey* wrapped_private_key) {
RETURN_CERTIFICATE_STATE_CANNOT_HANDLE_IF_UNINITIALIZED();
RETURN_CERTIFICATE_STATE_CANNOT_HANDLE_IF_NULL(certificate);
RETURN_CERTIFICATE_STATE_CANNOT_HANDLE_IF_NULL(wrapped_private_key);
std::string certificate_file_name;
if (!GetOemCertificateFileName(&certificate_file_name)) {
LOGW("Unable to find certificate file name");
return kCannotHandle;
}
video_widevine_client::sdk::File file;
if (RetrieveHashedFile(certificate_file_name, &file) != kNoError) {
LOGW("Unable to retrieve certificate file");
return kCertificateNotFound;
}
if (file.type() != video_widevine_client::sdk::File::OEM_CERTIFICATE) {
LOGE("Certificate file is of incorrect file type: type = %d",
static_cast<int>(file.type()));
return kCertificateInvalid;
}
if (file.version() != video_widevine_client::sdk::File::VERSION_1) {
LOGE("Certificate file is of incorrect file version: version = %d",
static_cast<int>(file.version()));
return kCertificateInvalid;
}
if (!file.has_oem_certificate()) {
LOGE("Certificate not present");
return kCertificateInvalid;
}
const OemCertificate& oem_certificate = file.oem_certificate();
if (oem_certificate.certificate().empty() ||
oem_certificate.wrapped_private_key().empty()) {
LOGE("Empty certificate or private key");
return kCertificateInvalid;
}
*certificate = oem_certificate.certificate();
wrapped_private_key->Clear();
wrapped_private_key->set_key(oem_certificate.wrapped_private_key());
switch (oem_certificate.key_type()) {
case OemCertificate::RSA:
wrapped_private_key->set_type(wvcdm::CryptoWrappedKey::kRsa);
break;
case OemCertificate::ECC:
wrapped_private_key->set_type(wvcdm::CryptoWrappedKey::kEcc);
break;
default:
LOGW("Unknown key type, defaulting to RSA: type = %d",
oem_certificate.key_type());
wrapped_private_key->set_type(wvcdm::CryptoWrappedKey::kRsa);
break;
}
return kCertificateValid;
}
bool DeviceFiles::HasOemCertificate() {
RETURN_FALSE_IF_UNINITIALIZED();
std::string certificate_file_name;
if (!GetOemCertificateFileName(&certificate_file_name)) {
return false;
}
return FileExists(certificate_file_name);
}
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<int>(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_index(license_data.usage_entry_index);
if (!license_data.drm_certificate.empty()) {
DeviceCertificate* device_certificate = license->mutable_drm_certificate();
if (!SetDeviceCertificate(license_data.drm_certificate,
license_data.wrapped_private_key,
device_certificate))
return false;
}
std::string serialized_file;
file.SerializeToString(&serialized_file);
UniqueLock lock(reserved_license_ids_mutex_);
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 = %s",
ResponseTypeToString(*result));
return false;
}
if (file.type() != video_widevine_client::sdk::File::LICENSE) {
LOGE("Incorrect file type: type = %d, expected_type = %d",
static_cast<int>(file.type()),
static_cast<int>(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<int>(file.version()),
static_cast<int>(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<int>(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_index =
static_cast<uint32_t>(license.usage_entry_index());
if (!license.has_drm_certificate()) {
license_data->drm_certificate.clear();
license_data->wrapped_private_key.Clear();
return true;
}
return ExtractFromDeviceCertificate(license.drm_certificate(),
&license_data->drm_certificate,
&license_data->wrapped_private_key);
}
DeviceFiles::ResponseType DeviceFiles::StoreAtscLicense(
const CdmKeySetId& key_set_id, const std::string& serialized_data) {
return StoreFileWithHash(key_set_id + kLicenseFileNameExt, serialized_data);
}
bool DeviceFiles::DeleteLicense(const std::string& key_set_id) {
RETURN_FALSE_IF_UNINITIALIZED();
return RemoveFile(key_set_id + kLicenseFileNameExt);
}
bool DeviceFiles::ListLicenses(std::vector<std::string>* key_set_ids) {
RETURN_FALSE_IF_UNINITIALIZED();
RETURN_FALSE_IF_NULL(key_set_ids);
// Get list of filenames
std::vector<std::string> 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();
UniqueLock lock(reserved_license_ids_mutex_);
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();
UniqueLock lock(reserved_license_ids_mutex_);
reserved_license_ids_.insert(key_set_id);
return true;
}
bool DeviceFiles::UnreserveLicenseId(const std::string& key_set_id) {
RETURN_FALSE_IF_UNINITIALIZED();
UniqueLock lock(reserved_license_ids_mutex_);
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 UsageEntry& usage_entry,
UsageEntryIndex usage_entry_index, const std::string& drm_certificate,
const CryptoWrappedKey& wrapped_private_key) {
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_index(usage_entry_index);
if (drm_certificate.size() > 0) {
uint32_t drm_certificate_id;
if (!FindOrInsertUsageCertificate(drm_certificate, wrapped_private_key,
usage_info, &drm_certificate_id)) {
LOGE("Unable to insert a certificate in the usage certificate cache");
return false;
}
provider_session->set_drm_certificate_id(drm_certificate_id);
}
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<std::string>* ksids,
std::vector<std::string>* 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();
const int num_records = file.usage_info().sessions_size();
for (int 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;
}
const int num_records = static_cast<int>(file.usage_info().sessions_size());
for (int 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 CdmKeySetId& key_set_id) {
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) {
const auto& session = usage_info->sessions(index);
if (session.key_set_id() == key_set_id) {
found = true;
break;
}
}
if (!found) {
LOGE("Unable to find usage info: key_set_id = %s", IdToString(key_set_id));
return false;
}
LOGD("Deleting usage info: key_set_id = %s, pst = %s", IdToString(key_set_id),
wvutil::b2a_hex(usage_info->sessions(index).token()).c_str());
google::protobuf::RepeatedPtrField<UsageInfo_ProviderSession>* sessions =
usage_info->mutable_sessions();
if (index < usage_info->sessions_size() - 1) {
sessions->SwapElements(index, usage_info->sessions_size() - 1);
}
sessions->RemoveLast();
UsageCertificateCacheCleanUp(usage_info);
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<std::string>* 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<std::string>& 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) {
UsageCertificateCacheCleanUp(file.mutable_usage_info());
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,
const std::string& provider_session_token,
CdmKeyMessage* license_request,
CdmKeyResponse* license,
UsageEntry* usage_entry,
UsageEntryIndex* usage_entry_index,
std::string* drm_certificate,
CryptoWrappedKey* wrapped_private_key) {
RETURN_FALSE_IF_UNINITIALIZED();
RETURN_FALSE_IF_NULL(license_request);
RETURN_FALSE_IF_NULL(license);
RETURN_FALSE_IF_NULL(usage_entry);
RETURN_FALSE_IF_NULL(usage_entry_index);
RETURN_FALSE_IF_NULL(drm_certificate);
RETURN_FALSE_IF_NULL(wrapped_private_key);
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_index = static_cast<UsageEntryIndex>(
file.usage_info().sessions(index).usage_entry_index());
if (!file.usage_info().sessions(index).has_drm_certificate_id()) {
drm_certificate->clear();
wrapped_private_key->Clear();
return true;
}
if (!FindUsageCertificate(
file.usage_info().sessions(index).drm_certificate_id(),
file.usage_info().drm_certificate_cache(), drm_certificate,
wrapped_private_key)) {
LOGE("Unable to find DRM certificate information from usage cache");
return false;
}
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, UsageEntry* usage_entry,
UsageEntryIndex* usage_entry_index, std::string* drm_certificate,
CryptoWrappedKey* wrapped_private_key) {
RETURN_FALSE_IF_UNINITIALIZED();
RETURN_FALSE_IF_NULL(license_request);
RETURN_FALSE_IF_NULL(license);
RETURN_FALSE_IF_NULL(usage_entry);
RETURN_FALSE_IF_NULL(usage_entry_index);
RETURN_FALSE_IF_NULL(drm_certificate);
RETURN_FALSE_IF_NULL(wrapped_private_key);
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_index = static_cast<UsageEntryIndex>(
file.usage_info().sessions(index).usage_entry_index());
if (!file.usage_info().sessions(index).has_drm_certificate_id()) {
drm_certificate->clear();
wrapped_private_key->Clear();
return true;
}
if (!FindUsageCertificate(
file.usage_info().sessions(index).drm_certificate_id(),
file.usage_info().drm_certificate_cache(), drm_certificate,
wrapped_private_key)) {
LOGE("Unable to find DRM certificate information from usage cache");
return false;
}
return true;
}
}
return false;
}
bool DeviceFiles::StoreUsageInfo(const std::string& usage_info_file_name,
const std::vector<CdmUsageData>& 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_index(usage_data[i].usage_entry_index);
if (usage_data[i].drm_certificate.size() > 0) {
uint32_t drm_certificate_id;
if (!FindOrInsertUsageCertificate(usage_data[i].drm_certificate,
usage_data[i].wrapped_private_key,
usage_info, &drm_certificate_id)) {
LOGE("Unable to insert a certificate in the usage certificate cache");
return false;
}
provider_session->set_drm_certificate_id(drm_certificate_id);
}
}
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 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;
}
video_widevine_client::sdk::UsageInfo* usage_info = file.mutable_usage_info();
int index = 0;
for (; index < usage_info->sessions_size(); ++index) {
// Use key set ID to identify usage info. PST is not guaranteed
// to be unique.
if (usage_info->sessions(index).key_set_id() == usage_data.key_set_id)
break;
}
if (index == usage_info->sessions_size()) {
LOGE("Failed to find usage info: key_set_id = %s",
IdToString(usage_data.key_set_id));
return false;
}
video_widevine_client::sdk::UsageInfo::ProviderSession* session =
usage_info->mutable_sessions(index);
// Verify that the PST are the same.
if (session->token() != usage_data.provider_session_token) {
LOGE("Mismatch PST: key_set_id = %s", IdToString(usage_data.key_set_id));
return false;
}
// Update session.
session->set_license_request(usage_data.license_request);
session->set_license(usage_data.license);
session->set_usage_entry(usage_data.usage_entry);
session->set_usage_entry_index(usage_data.usage_entry_index);
if (usage_data.drm_certificate.size() > 0) {
uint32_t drm_certificate_id;
if (!FindOrInsertUsageCertificate(usage_data.drm_certificate,
usage_data.wrapped_private_key,
usage_info, &drm_certificate_id)) {
LOGE("Unable to find a certificate in to update the usage info");
return false;
}
session->set_drm_certificate_id(drm_certificate_id);
}
std::string serialized_file;
file.SerializeToString(&serialized_file);
return StoreFileWithHash(usage_info_file_name, serialized_file) == kNoError;
}
bool DeviceFiles::RetrieveUsageInfo(const std::string& usage_info_file_name,
std::vector<CdmUsageData>* 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_index = static_cast<uint32_t>(
file.usage_info().sessions(i).usage_entry_index());
if (!file.usage_info().sessions(i).has_drm_certificate_id()) {
(*usage_data)[i].drm_certificate.clear();
(*usage_data)[i].wrapped_private_key.Clear();
} else {
if (!FindUsageCertificate(
file.usage_info().sessions(i).drm_certificate_id(),
file.usage_info().drm_certificate_cache(),
&(*usage_data)[i].drm_certificate,
&(*usage_data)[i].wrapped_private_key)) {
LOGW("Unable to find DRM certificate information from usage cache");
}
}
}
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_index = static_cast<uint32_t>(
file.usage_info().sessions(index).usage_entry_index());
if (!file.usage_info().sessions(index).has_drm_certificate_id()) {
usage_data->drm_certificate.clear();
usage_data->wrapped_private_key.Clear();
return true;
}
if (!FindUsageCertificate(
file.usage_info().sessions(index).drm_certificate_id(),
file.usage_info().drm_certificate_cache(),
&usage_data->drm_certificate, &usage_data->wrapped_private_key)) {
LOGE("Unable to find DRM certificate information from usage cache");
return false;
}
return true;
}
}
return false;
}
bool DeviceFiles::ListUsageInfoFiles(
std::vector<std::string>* usage_info_file_names) {
RETURN_FALSE_IF_UNINITIALIZED();
RETURN_FALSE_IF_NULL(usage_info_file_names);
// Get list of filenames
std::vector<std::string> 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<uint8_t>& 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<uint8_t>* 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<int>(file.type()));
return false;
}
if (file.version() != video_widevine_client::sdk::File::VERSION_1) {
LOGE("Incorrect file version: version = %d",
static_cast<int>(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<int>(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 UsageTableHeader& table_header,
const std::vector<CdmUsageEntryInfo>& entry_info_list) {
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_table_header(table_header);
for (size_t i = 0; i < entry_info_list.size(); ++i) {
UsageTableInfo_UsageEntryInfo* info =
usage_table_info->add_entry_info_list();
info->set_key_set_id(entry_info_list[i].key_set_id);
switch (entry_info_list[i].storage_type) {
case kStorageLicense:
info->set_storage(
UsageTableInfo_UsageEntryInfo_UsageEntryStorage_LICENSE);
info->set_last_use_time(entry_info_list[i].last_use_time);
info->set_offline_license_expiry_time(
entry_info_list[i].offline_license_expiry_time);
break;
case kStorageUsageInfo:
info->set_storage(
UsageTableInfo_UsageEntryInfo_UsageEntryStorage_USAGE_INFO);
info->set_usage_info_file_name(entry_info_list[i].usage_info_file_name);
info->set_last_use_time(entry_info_list[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(
UsageTableHeader* table_header,
std::vector<CdmUsageEntryInfo>* entry_info_list, bool* lru_upgrade) {
RETURN_FALSE_IF_UNINITIALIZED();
RETURN_FALSE_IF_NULL(table_header);
RETURN_FALSE_IF_NULL(entry_info_list);
RETURN_FALSE_IF_NULL(lru_upgrade);
video_widevine_client::sdk::File file;
if (RetrieveHashedFile(GetUsageTableFileName(), &file) != kNoError) {
LOGW("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<int>(file.type()),
static_cast<int>(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<int>(file.version()),
static_cast<int>(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();
*table_header = usage_table_info.table_header();
entry_info_list->resize(usage_table_info.entry_info_list_size());
for (int i = 0; i < usage_table_info.entry_info_list_size(); ++i) {
const UsageTableInfo_UsageEntryInfo& info =
usage_table_info.entry_info_list(i);
(*entry_info_list)[i].key_set_id = info.key_set_id();
switch (info.storage()) {
case UsageTableInfo_UsageEntryInfo_UsageEntryStorage_LICENSE:
(*entry_info_list)[i].storage_type = kStorageLicense;
(*entry_info_list)[i].last_use_time = info.last_use_time();
(*entry_info_list)[i].offline_license_expiry_time =
info.offline_license_expiry_time();
break;
case UsageTableInfo_UsageEntryInfo_UsageEntryStorage_USAGE_INFO:
(*entry_info_list)[i].storage_type = kStorageUsageInfo;
(*entry_info_list)[i].usage_info_file_name =
info.usage_info_file_name();
(*entry_info_list)[i].last_use_time = info.last_use_time();
break;
case UsageTableInfo_UsageEntryInfo_UsageEntryStorage_UNKNOWN:
default:
(*entry_info_list)[i].storage_type = kStorageTypeUnknown;
break;
}
}
return true;
}
bool DeviceFiles::DeleteUsageTableInfo() {
RETURN_FALSE_IF_UNINITIALIZED();
return RemoveFile(GetUsageTableFileName());
}
bool DeviceFiles::HasCertificate(CertificateType certificate_type) {
RETURN_FALSE_IF_UNINITIALIZED();
std::string certificate_file_name;
if (!GetCertificateFileName(certificate_type, &certificate_file_name))
return false;
return FileExists(certificate_file_name);
}
bool DeviceFiles::StoreOkpInfo(const okp::SystemFallbackInfo& info) {
using StoredOkpInfo = video_widevine_client::sdk::OtaKeyboxProvisioningInfo;
using okp::SystemState;
RETURN_FALSE_IF_UNINITIALIZED();
if (security_level_ != kSecurityLevelL1) {
LOGE("OKP info is only supported by L1: level = %d",
static_cast<int>(security_level_));
return false;
}
video_widevine_client::sdk::File file;
file.set_type(video_widevine_client::sdk::File::OKP_INFO);
file.set_version(video_widevine_client::sdk::File::VERSION_1);
StoredOkpInfo* stored_info = file.mutable_okp_info();
switch (info.state()) {
case SystemState::kNeedsProvisioning:
stored_info->set_state(StoredOkpInfo::OKP_NEEDS_PROVISIONING);
break;
case SystemState::kFallbackMode:
stored_info->set_state(StoredOkpInfo::OKP_FALLBACK_MODE);
break;
case SystemState::kProvisioned:
stored_info->set_state(StoredOkpInfo::OKP_PROVISIONED);
break;
case SystemState::kUnknown:
default:
LOGE("Unexpected OKP state: state = %d", static_cast<int>(info.state()));
return false;
}
if (info.first_checked_time() <= 0) {
LOGE("OKP first checked time is missing");
return false;
}
stored_info->set_first_checked_time(info.first_checked_time());
if (info.state() == SystemState::kProvisioned) {
if (!info.HasProvisioningTime()) {
LOGE("OKP set as provisioned, but missing provisioning time");
return false;
}
stored_info->set_provisioning_time(info.provisioning_time());
} else if (info.state() == SystemState::kFallbackMode) {
if (!info.HasBackoffStartTime() || !info.HasBackoffDuration()) {
LOGE("OKP fallback information is missing ");
return false;
}
stored_info->set_backoff_start_time(info.backoff_start_time());
stored_info->set_backoff_duration(info.backoff_duration());
} else {
if (info.HasBackoffDuration()) {
// Store backoff duration from before.
stored_info->set_backoff_duration(info.backoff_duration());
}
}
std::string serialized_file;
file.SerializeToString(&serialized_file);
return StoreFileWithHash(GetOkpInfoFileName(), serialized_file) == kNoError;
}
bool DeviceFiles::RetrieveOkpInfo(okp::SystemFallbackInfo* info) {
using StoredOkpInfo = video_widevine_client::sdk::OtaKeyboxProvisioningInfo;
using okp::SystemState;
RETURN_FALSE_IF_UNINITIALIZED();
RETURN_FALSE_IF_NULL(info);
info->Clear();
if (security_level_ != kSecurityLevelL1) {
LOGE("OKP info is only supported by L1: level = %d",
static_cast<int>(security_level_));
return false;
}
// File meta-data validation.
video_widevine_client::sdk::File file;
if (RetrieveHashedFile(GetOkpInfoFileName(), &file) != kNoError) {
LOGW("Unable to retrieve OKP info file");
return false;
}
if (file.type() != video_widevine_client::sdk::File::OKP_INFO) {
LOGE("Incorrect file type: type = %d, expected_type = %d",
static_cast<int>(file.type()),
static_cast<int>(video_widevine_client::sdk::File::OKP_INFO));
return false;
}
if (file.version() != video_widevine_client::sdk::File::VERSION_1) {
LOGE("Incorrect file version: version = %d, expected_version = %d",
static_cast<int>(file.version()),
static_cast<int>(video_widevine_client::sdk::File::VERSION_1));
return false;
}
if (!file.has_okp_info()) {
// OKP info is only stored if at least 1 field is non-empty. This
// must be an error.
LOGD("OKP info is not present in file");
return false;
}
const StoredOkpInfo& stored_info = file.okp_info();
switch (stored_info.state()) {
case StoredOkpInfo::OKP_NEEDS_PROVISIONING:
info->SetState(SystemState::kNeedsProvisioning);
break;
case StoredOkpInfo::OKP_FALLBACK_MODE:
info->SetState(SystemState::kFallbackMode);
break;
case StoredOkpInfo::OKP_PROVISIONED:
info->SetState(SystemState::kProvisioned);
break;
case StoredOkpInfo::OKP_UNKNOWN:
default:
LOGE("Unexpected OKP state: stored_state = %d",
static_cast<int>(stored_info.state()));
return false;
}
if (stored_info.first_checked_time() <= 0) {
LOGE("OKP first check time not present");
info->Clear();
return false;
}
info->SetFirstCheckedTime(stored_info.first_checked_time());
if (info->state() == SystemState::kProvisioned) {
if (stored_info.provisioning_time() <= 0) {
LOGE("OKP set as provisioned, but missing provisioning time");
info->Clear();
return false;
}
info->SetProvisioningTime(stored_info.provisioning_time());
return true;
}
if (info->state() == SystemState::kFallbackMode) {
if (stored_info.backoff_start_time() <= 0 ||
stored_info.backoff_duration() <= 0) {
LOGE("OKP backoff information is missing");
info->Clear();
return false;
}
info->SetBackoffStartTime(stored_info.backoff_start_time());
info->SetBackoffDuration(stored_info.backoff_duration());
return true;
}
// Provisioned.
if (stored_info.backoff_duration() > 0) {
info->SetBackoffDuration(stored_info.backoff_duration());
}
return true;
}
bool DeviceFiles::DeleteOkpInfo() {
RETURN_FALSE_IF_UNINITIALIZED();
if (security_level_ != kSecurityLevelL1) {
LOGE("OKP info is only supported by L1: level = %d",
static_cast<int>(security_level_));
return false;
}
return RemoveFile(GetOkpInfoFileName());
}
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, wvutil::FileSystem::kCreate | wvutil::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<ssize_t>(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)) {
LOGW("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, wvutil::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<std::string>* 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);
}
bool DeviceFiles::GetCertificateFileName(CertificateType certificate_type,
std::string* certificate_file_name) {
RETURN_FALSE_IF_NULL(certificate_file_name);
switch (certificate_type) {
case kCertificateDefault:
*certificate_file_name = wvutil::kCertificateFileName;
return true;
case kCertificateLegacy:
*certificate_file_name = wvutil::kLegacyCertificateFileName;
return true;
case kCertificateAtsc:
*certificate_file_name = wvutil::kAtscCertificateFileName;
return true;
default:
return false;
}
}
bool DeviceFiles::GetOemCertificateFileName(
std::string* certificate_file_name) {
RETURN_FALSE_IF_NULL(certificate_file_name);
*certificate_file_name = wvutil::kOemCertificateFileName;
return true;
}
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::GetOkpInfoFileName() { return kOkpInfoFileName; }
std::string DeviceFiles::GetFileNameSafeHash(const std::string& input) {
return wvutil::Base64SafeEncode(Md5Hash(input));
}
} // namespace wvcdm