Files
android/libwvdrmengine/cdm/core/src/device_files.cpp
Alex Dale a80a9fef26 Added mutex for reserved_license_ids_.
[ Merge of http://go/wvgerrit/150630 ]

DeviceFiles uses a static variable for tracking license IDs which
have been reserved by a CDM session before officially storing the
license on the device.  This variable was not protected by a mutex,
and a rare race condition would arise, either crashing the service
or getting it stuck in a loop.

This CL adds a mutex for protecting the set of reserved IDs.

Bug: 226555704
Test: device_files_unittest
Change-Id: Icdea88673c76c267b4b7db79697ec52ae8e2581e
2022-04-22 13:53:11 -07:00

2150 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_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);
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_number =
static_cast<uint32_t>(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();
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 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();
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 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",
wvutil::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 = static_cast<uint32_t>(
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 = static_cast<uint32_t>(
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 = static_cast<uint32_t>(
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 = static_cast<uint32_t>(
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);
}
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) {
LOGE("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