[ Merge of http://go/wvgerrit/128163 ] In android S, we added a feature b/169740403 [Limited lifespan DRM certificates with license preservation] Due to uncertainties of when the provisioning service will launch, we are disabling expiration for legacy DRM certificates. If the feature does not launch in time, existing DRM certificates will expire and be replaced. Offline licenses associated with these expired DRM certificates will fail to load. Expiration of legacy certificates will be reenabled at a later time. The main portion of feature, the issuing of new DRM certificates with expiration time will still be supported. Bug: 192428783 Bug: 169740403 Test: WV unit/integration tests Change-Id: I1d1184249848f215953a837f369528d3b74c9618
1853 lines
64 KiB
C++
1853 lines
64 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::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 {
|
|
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 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);
|
|
|
|
const bool has_certificate = device_certificate.has_certificate();
|
|
const 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_;
|
|
|
|
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 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) {
|
|
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.
|
|
Clock clock;
|
|
const int64_t current_time = clock.GetCurrentTime();
|
|
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;
|
|
|
|
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::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_number(license_data.usage_entry_number);
|
|
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);
|
|
|
|
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_number = license.usage_entry_number();
|
|
|
|
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);
|
|
}
|
|
|
|
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();
|
|
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, 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_number(usage_entry_number);
|
|
|
|
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();
|
|
|
|
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<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,
|
|
std::string* usage_entry,
|
|
uint32_t* usage_entry_number,
|
|
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_number);
|
|
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_number =
|
|
file.usage_info().sessions(index).usage_entry_number();
|
|
|
|
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, std::string* usage_entry,
|
|
uint32_t* usage_entry_number, 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_number);
|
|
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_number =
|
|
file.usage_info().sessions(index).usage_entry_number();
|
|
|
|
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_number(usage_data[i].usage_entry_number);
|
|
|
|
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 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);
|
|
|
|
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;
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
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_number =
|
|
file.usage_info().sessions(i).usage_entry_number();
|
|
|
|
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_number =
|
|
file.usage_info().sessions(index).usage_entry_number();
|
|
|
|
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 CdmUsageTableHeader& usage_table_header,
|
|
const std::vector<CdmUsageEntryInfo>& 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<CdmUsageEntryInfo>* 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<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();
|
|
|
|
*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());
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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<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, 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* file_name) {
|
|
RETURN_FALSE_IF_NULL(file_name);
|
|
switch (certificate_type) {
|
|
case kCertificateDefault:
|
|
*file_name = kCertificateFileName;
|
|
return true;
|
|
case kCertificateLegacy:
|
|
*file_name = kLegacyCertificateFileName;
|
|
return true;
|
|
case kCertificateAtsc:
|
|
*file_name = kAtscCertificateFileName;
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
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) {
|
|
return Base64SafeEncode(Md5Hash(input));
|
|
}
|
|
|
|
} // namespace wvcdm
|