Merges to android Pi release (part: 1)
Below are a set of CLs being merged from the wv cdm repo to the android repo. * Fix handling of OEM Cert public key. Author: Srujan Gaddam <srujzs@google.com> [ Merge of http://go/wvgerrit/27921 ] This is a potential fix for b/36656190. Set aside public key on first call to get the public key, and use it afterwards. This gets rid of extra calls to OEMCrypto_GetOEMPublicCertificate(), which has side-effect of staging the OEM private key. This also fixes a problem where the public cert string was not being trimmed to match the size returned by OEMCrypto_GetOEMPublicCertificate(). * Complete provisioning request/response for Provisioning 3.0 Author: Gene Morgan <gmorgan@google.com> [ Merge of http://go/wvgerrit/27780 ] Fix bug on provisioning request path where GenerateDerivedKeys() was being called when preparing to generate the signature. Add message signature verification, and call correct OEMCrypto routine to rewrap the private key (OEMCrypto_RewrapDeviceRSAKey30). * Implement Cdm::deleteAllUsageRecords() Author: Gene Morgan <gmorgan@google.com> [ Merge of http://go/wvgerrit/27780 ] Delete all usage records for current origin. Removes usage records from file system and retains the PSTs. The deletes any usage entries matching those PSTs held by OEMCrypto. BUG: 35319024 * Remove stringencoders library from third_party. Author: Jacob Trimble <modmaker@google.com> [ Merge of http://go/wvgerrit/27585 ] We have a fork of the stringencoders library that we use for base64 encoding. This reimplements base64 encoding to remove the extra dependency and to reduce the amount of code. * Add Cdm::deleteUsageRecord() based on key_set_id. Author: Gene Morgan <gmorgan@google.com> [ Merge of http://go/wvgerrit/27605 ] Delete specified usage record from file system usage info and from OEMCrypto. BUG: 35319024 * Modifiable OEMCrypto Author: Fred Gylys-Colwell <fredgc@google.com> [ Merge of http://go/wvgerrit/24729 ] This CL adds a new variant of the OEMCrypto mock code that adjusts its behavior based on a configuration file. This is intended for testing. For example, a tester can set current_hdcp to 2 in the options.txt file, push it to the device, and verify that a license is granted for HDCP 2.0. Then the tester can edit the value of current_hdcp to 1 and push the file to the device. Playback should stop because the license is no longer valid. This variant uses a real level 1 liboemcrypto.so to push data to a secure buffer. That means we can test playback for a license that requires secure buffers on an Android device with real secure buffers. BUG: 35141278 BUG: 37353534 BUG: 71650075 Test: Not currently passing. Will be addressed in a subsequent commit in the chain. Change-Id: I58443c510919e992bb455192e70373490a00e2b6
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -12,11 +12,11 @@
|
||||
#include "clock.h"
|
||||
#include "file_store.h"
|
||||
#include "log.h"
|
||||
#include "metrics_front_end.h"
|
||||
#include "properties.h"
|
||||
#include "string_conversions.h"
|
||||
#include "wv_cdm_constants.h"
|
||||
#include "wv_cdm_event_listener.h"
|
||||
#include "usage_table_header.h"
|
||||
|
||||
namespace {
|
||||
const size_t kKeySetIdLength = 14;
|
||||
@@ -24,10 +24,10 @@ const size_t kKeySetIdLength = 14;
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
CdmSession::CdmSession(FileSystem* file_system,
|
||||
metrics::SessionMetrics* metrics) :
|
||||
metrics_(metrics),
|
||||
CdmSession::CdmSession(FileSystem* file_system) :
|
||||
initialized_(false),
|
||||
closed_(false),
|
||||
crypto_session_(new CryptoSession(&metrics_)),
|
||||
file_handle_(new DeviceFiles(file_system)),
|
||||
license_received_(false),
|
||||
is_offline_(false),
|
||||
@@ -39,14 +39,8 @@ CdmSession::CdmSession(FileSystem* file_system,
|
||||
has_decrypted_since_last_report_(false),
|
||||
is_initial_usage_update_(true),
|
||||
is_usage_update_needed_(false),
|
||||
usage_support_type_(kNonSecureUsageSupport),
|
||||
usage_table_header_(NULL),
|
||||
usage_entry_number_(0),
|
||||
mock_license_parser_in_use_(false),
|
||||
mock_policy_engine_in_use_(false) {
|
||||
assert(metrics_); // metrics_ must not be null.
|
||||
crypto_metrics_ = metrics_->GetCryptoMetrics();
|
||||
crypto_session_.reset(new CryptoSession(crypto_metrics_));
|
||||
life_span_.Start();
|
||||
}
|
||||
|
||||
@@ -57,10 +51,7 @@ CdmSession::~CdmSession() {
|
||||
}
|
||||
Properties::RemoveSessionPropertySet(session_id_);
|
||||
|
||||
if (metrics_) {
|
||||
M_RECORD(metrics_, cdm_session_life_span_, life_span_.AsMs());
|
||||
metrics_->SetCompleted();
|
||||
}
|
||||
M_RECORD(&metrics_, cdm_session_life_span_, life_span_.AsMs());
|
||||
}
|
||||
|
||||
CdmResponseType CdmSession::Init(
|
||||
@@ -85,28 +76,23 @@ CdmResponseType CdmSession::Init(
|
||||
}
|
||||
CdmResponseType sts;
|
||||
M_TIME(
|
||||
sts = crypto_session_->Open(requested_security_level_),
|
||||
crypto_metrics_,
|
||||
sts = crypto_session_->Open(
|
||||
requested_security_level_),
|
||||
&metrics_,
|
||||
crypto_session_open_,
|
||||
sts,
|
||||
requested_security_level_);
|
||||
if (NO_ERROR != sts) return sts;
|
||||
|
||||
security_level_ = crypto_session_->GetSecurityLevel();
|
||||
crypto_metrics_->crypto_session_security_level_.Record(security_level_);
|
||||
|
||||
M_TIME(
|
||||
security_level_ = crypto_session_->GetSecurityLevel(),
|
||||
&metrics_,
|
||||
crypto_session_get_security_level_,
|
||||
security_level_);
|
||||
if (!file_handle_->Init(security_level_)) {
|
||||
LOGE("CdmSession::Init: Unable to initialize file handle");
|
||||
return SESSION_FILE_HANDLE_INIT_ERROR;
|
||||
}
|
||||
|
||||
if (crypto_session_->GetUsageSupportType(&usage_support_type_) == NO_ERROR) {
|
||||
if (usage_support_type_ == kUsageEntrySupport)
|
||||
usage_table_header_ = crypto_session_->GetUsageTableHeader();
|
||||
} else {
|
||||
usage_support_type_ = kNonSecureUsageSupport;
|
||||
}
|
||||
|
||||
// Device Provisioning state is not yet known.
|
||||
// If not using certificates, then Keybox is client token for license
|
||||
// requests.
|
||||
@@ -124,9 +110,13 @@ CdmResponseType CdmSession::Init(
|
||||
LOGW("CdmSession::Init: Properties::use_certificates_as_identification() "
|
||||
"is not set - using Keybox for license requests (not recommended).");
|
||||
|
||||
bool get_client_token_sts = crypto_session_->GetClientToken(&client_token);
|
||||
crypto_metrics_->crypto_session_get_token_.Increment(
|
||||
get_client_token_sts);
|
||||
bool get_client_token_sts;
|
||||
M_TIME(
|
||||
get_client_token_sts = crypto_session_->GetClientToken(
|
||||
&client_token),
|
||||
&metrics_,
|
||||
crypto_session_get_token_,
|
||||
get_client_token_sts);
|
||||
if (!get_client_token_sts) {
|
||||
return SESSION_INIT_ERROR_1;
|
||||
}
|
||||
@@ -141,7 +131,7 @@ CdmResponseType CdmSession::Init(
|
||||
M_TIME(
|
||||
load_cert_sts = crypto_session_->LoadCertificatePrivateKey(
|
||||
wrapped_key),
|
||||
crypto_metrics_,
|
||||
&metrics_,
|
||||
crypto_session_load_certificate_private_key_,
|
||||
load_cert_sts);
|
||||
if(!load_cert_sts) {
|
||||
@@ -163,7 +153,6 @@ CdmResponseType CdmSession::Init(
|
||||
|
||||
session_id_ =
|
||||
Properties::AlwaysUseKeySetIds() ? key_set_id_ : GenerateSessionId();
|
||||
metrics_->SetSessionId(session_id_);
|
||||
|
||||
if (session_id_.empty()) {
|
||||
LOGE("CdmSession::Init: empty session ID");
|
||||
@@ -191,54 +180,33 @@ CdmResponseType CdmSession::Init(
|
||||
|
||||
CdmResponseType CdmSession::RestoreOfflineSession(
|
||||
const CdmKeySetId& key_set_id, const CdmLicenseType license_type) {
|
||||
if (!key_set_id_.empty()) {
|
||||
file_handle_->UnreserveLicenseId(key_set_id_);
|
||||
}
|
||||
key_set_id_ = key_set_id;
|
||||
|
||||
DeviceFiles::LicenseState license_state;
|
||||
int64_t playback_start_time;
|
||||
int64_t last_playback_time;
|
||||
int64_t grace_period_end_time;
|
||||
std::string usage_entry;
|
||||
|
||||
if (!file_handle_->RetrieveLicense(
|
||||
key_set_id, &license_state, &offline_init_data_, &key_request_,
|
||||
&key_response_, &offline_key_renewal_request_,
|
||||
&offline_key_renewal_response_, &offline_release_server_url_,
|
||||
&playback_start_time, &last_playback_time, &grace_period_end_time,
|
||||
&app_parameters_, &usage_entry_, &usage_entry_number_)) {
|
||||
LOGE("CdmSession::RestoreOfflineSession: failed to retrieve license. "
|
||||
"key set id = %s", key_set_id.c_str());
|
||||
&app_parameters_, &usage_entry)) {
|
||||
LOGE("CdmSession::Init failed to retrieve license. key set id = %s",
|
||||
key_set_id.c_str());
|
||||
return GET_LICENSE_ERROR;
|
||||
}
|
||||
|
||||
// Do not restore a released offline license, unless a release retry
|
||||
if (!(license_type == kLicenseTypeRelease ||
|
||||
license_state == DeviceFiles::kLicenseStateActive)) {
|
||||
LOGE("CdmSession::RestoreOfflineSession: invalid offline license state = "
|
||||
"%d, type = %d", license_state, license_type);
|
||||
LOGE("CdmSession::Init invalid offline license state = %d, type = %d",
|
||||
license_state, license_type);
|
||||
return GET_RELEASED_LICENSE_ERROR;
|
||||
}
|
||||
|
||||
std::string provider_session_token;
|
||||
if (!license_parser_->ExtractProviderSessionToken(
|
||||
key_response_, &provider_session_token)) {
|
||||
provider_session_token.clear();
|
||||
}
|
||||
|
||||
if (usage_support_type_ == kUsageEntrySupport &&
|
||||
provider_session_token.size() > 0 &&
|
||||
usage_table_header_ != nullptr) {
|
||||
CdmResponseType sts = usage_table_header_->LoadEntry(crypto_session_.get(),
|
||||
usage_entry_,
|
||||
usage_entry_number_);
|
||||
if (sts != NO_ERROR) {
|
||||
LOGE("CdmSession::RestoreOfflineSession: failed to load usage entry = %d",
|
||||
sts);
|
||||
return sts;
|
||||
}
|
||||
}
|
||||
|
||||
if (license_type == kLicenseTypeRelease) {
|
||||
if (!license_parser_->RestoreLicenseForRelease(key_request_,
|
||||
key_response_)) {
|
||||
@@ -247,28 +215,11 @@ CdmResponseType CdmSession::RestoreOfflineSession(
|
||||
} else {
|
||||
if (!license_parser_->RestoreOfflineLicense(
|
||||
key_request_, key_response_, offline_key_renewal_response_,
|
||||
playback_start_time, last_playback_time, grace_period_end_time,
|
||||
this)) {
|
||||
playback_start_time, last_playback_time, grace_period_end_time)) {
|
||||
return RESTORE_OFFLINE_LICENSE_ERROR_2;
|
||||
}
|
||||
}
|
||||
|
||||
if (usage_support_type_ == kUsageEntrySupport &&
|
||||
provider_session_token.size() > 0 &&
|
||||
usage_table_header_ != nullptr) {
|
||||
CdmResponseType sts =
|
||||
usage_table_header_->UpdateEntry(crypto_session_.get(), &usage_entry_);
|
||||
if (sts != NO_ERROR) {
|
||||
LOGE("CdmSession::RestoreOfflineSession failed to update usage entry = "
|
||||
"%d", sts);
|
||||
return sts;
|
||||
}
|
||||
if (!StoreLicense(license_state)) {
|
||||
LOGW("CdmSession::RestoreUsageSession: unable to save updated usage "
|
||||
"info");
|
||||
}
|
||||
}
|
||||
|
||||
license_received_ = true;
|
||||
is_offline_ = true;
|
||||
is_release_ = license_type == kLicenseTypeRelease;
|
||||
@@ -276,47 +227,14 @@ CdmResponseType CdmSession::RestoreOfflineSession(
|
||||
}
|
||||
|
||||
CdmResponseType CdmSession::RestoreUsageSession(
|
||||
const DeviceFiles::CdmUsageData& usage_data) {
|
||||
if (!key_set_id_.empty()) {
|
||||
file_handle_->UnreserveLicenseId(key_set_id_);
|
||||
}
|
||||
key_set_id_ = usage_data.key_set_id;
|
||||
key_request_ = usage_data.license_request;
|
||||
key_response_ = usage_data.license;
|
||||
usage_entry_ = usage_data.usage_entry;
|
||||
usage_entry_number_ = usage_data.usage_entry_number;
|
||||
usage_provider_session_token_ = usage_data.provider_session_token;
|
||||
|
||||
if (usage_support_type_ == kUsageEntrySupport &&
|
||||
usage_table_header_ != nullptr) {
|
||||
CdmResponseType sts = usage_table_header_->LoadEntry(
|
||||
crypto_session_.get(), usage_entry_, usage_entry_number_);
|
||||
if (sts != NO_ERROR) {
|
||||
LOGE("CdmSession::RestoreUsageSession: failed to load usage entry = %d",
|
||||
sts);
|
||||
return sts;
|
||||
}
|
||||
}
|
||||
const CdmKeyMessage& key_request, const CdmKeyResponse& key_response) {
|
||||
key_request_ = key_request;
|
||||
key_response_ = key_response;
|
||||
|
||||
if (!license_parser_->RestoreLicenseForRelease(key_request_, key_response_)) {
|
||||
return RELEASE_LICENSE_ERROR_2;
|
||||
}
|
||||
|
||||
if (usage_support_type_ == kUsageEntrySupport &&
|
||||
usage_table_header_ != nullptr) {
|
||||
CdmResponseType sts =
|
||||
usage_table_header_->UpdateEntry(crypto_session_.get(), &usage_entry_);
|
||||
if (sts != NO_ERROR) {
|
||||
LOGE("CdmSession::RestoreUsageSession: failed to update usage entry: %d",
|
||||
sts);
|
||||
return sts;
|
||||
}
|
||||
if (!UpdateUsageInfo()) {
|
||||
LOGW("CdmSession::RestoreUsageSession: unable to save updated usage "
|
||||
"info");
|
||||
}
|
||||
}
|
||||
|
||||
license_received_ = true;
|
||||
is_offline_ = false;
|
||||
is_release_ = true;
|
||||
@@ -442,57 +360,14 @@ CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response) {
|
||||
} else if (license_received_) { // renewal
|
||||
return RenewKey(key_response);
|
||||
} else {
|
||||
// If usage table header+entries are supported, preprocess the license
|
||||
// to see if it has a provider session token. If so a new entry needs
|
||||
// to be created.
|
||||
CdmResponseType sts;
|
||||
std::string provider_session_token;
|
||||
if (usage_support_type_ == kUsageEntrySupport &&
|
||||
usage_table_header_ != nullptr) {
|
||||
if (license_parser_->ExtractProviderSessionToken(
|
||||
key_response, &provider_session_token) &&
|
||||
!provider_session_token.empty()) {
|
||||
std::string app_id;
|
||||
GetApplicationId(&app_id);
|
||||
sts = usage_table_header_->AddEntry(
|
||||
crypto_session_.get(), is_offline_, key_set_id_,
|
||||
DeviceFiles::GetUsageInfoFileName(app_id), &usage_entry_number_);
|
||||
if (sts != NO_ERROR) return sts;
|
||||
}
|
||||
}
|
||||
sts = license_parser_->HandleKeyResponse(key_response);
|
||||
|
||||
// Update or delete entry if usage table header+entries are supported
|
||||
if (usage_support_type_ == kUsageEntrySupport &&
|
||||
!provider_session_token.empty() &&
|
||||
usage_table_header_ != nullptr) {
|
||||
if (sts != KEY_ADDED) {
|
||||
CdmResponseType sts =
|
||||
usage_table_header_->DeleteEntry(usage_entry_number_,
|
||||
file_handle_.get(),
|
||||
crypto_metrics_);
|
||||
if (sts != NO_ERROR) {
|
||||
LOGW("CdmSession::AddKey: Delete usage entry failed = %d", sts);
|
||||
}
|
||||
}
|
||||
}
|
||||
CdmResponseType sts = license_parser_->HandleKeyResponse(key_response);
|
||||
|
||||
if (sts != KEY_ADDED) return (sts == KEY_ERROR) ? ADD_KEY_ERROR : sts;
|
||||
|
||||
license_received_ = true;
|
||||
key_response_ = key_response;
|
||||
|
||||
if (is_offline_ || has_provider_session_token()) {
|
||||
if (has_provider_session_token() &&
|
||||
usage_support_type_ == kUsageEntrySupport &&
|
||||
usage_table_header_ != nullptr) {
|
||||
usage_table_header_->UpdateEntry(crypto_session_.get(), &usage_entry_);
|
||||
}
|
||||
|
||||
if (!is_offline_)
|
||||
usage_provider_session_token_ =
|
||||
license_parser_->provider_session_token();
|
||||
|
||||
if (is_offline_ || !license_parser_->provider_session_token().empty()) {
|
||||
sts = StoreLicense();
|
||||
if (sts != NO_ERROR) return sts;
|
||||
}
|
||||
@@ -595,7 +470,8 @@ CdmResponseType CdmSession::Decrypt(const CdmDecryptionParameters& params) {
|
||||
}
|
||||
has_decrypted_since_last_report_ = true;
|
||||
if (!is_usage_update_needed_) {
|
||||
is_usage_update_needed_ = has_provider_session_token();
|
||||
is_usage_update_needed_ =
|
||||
!license_parser_->provider_session_token().empty();
|
||||
}
|
||||
} else {
|
||||
Clock clock;
|
||||
@@ -614,7 +490,7 @@ CdmResponseType CdmSession::Decrypt(const CdmDecryptionParameters& params) {
|
||||
CdmResponseType CdmSession::GenerateRenewalRequest(
|
||||
CdmKeyRequest* key_request) {
|
||||
CdmResponseType status = license_parser_->PrepareKeyUpdateRequest(
|
||||
true, app_parameters_, NULL, &key_request->message, &key_request->url);
|
||||
true, app_parameters_, &key_request->message, &key_request->url);
|
||||
|
||||
key_request->type = kKeyRequestTypeRenewal;
|
||||
|
||||
@@ -644,32 +520,16 @@ CdmResponseType CdmSession::GenerateReleaseRequest(
|
||||
CdmKeyRequest* key_request) {
|
||||
is_release_ = true;
|
||||
CdmResponseType status = license_parser_->PrepareKeyUpdateRequest(
|
||||
false, app_parameters_, usage_table_header_ == NULL ? NULL : this,
|
||||
&key_request->message, &key_request->url);
|
||||
false, app_parameters_, &key_request->message,
|
||||
&key_request->url);
|
||||
|
||||
key_request->type = kKeyRequestTypeRelease;
|
||||
|
||||
if (KEY_MESSAGE != status) return status;
|
||||
|
||||
if (has_provider_session_token() &&
|
||||
usage_support_type_ == kUsageEntrySupport) {
|
||||
status = usage_table_header_->UpdateEntry(crypto_session_.get(),
|
||||
&usage_entry_);
|
||||
if (status != NO_ERROR) {
|
||||
LOGE("CdmSession::GenerateReleaseRequest: Update usage entry failed = "
|
||||
"%d", status);
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_offline_) { // Mark license as being released
|
||||
if (!StoreLicense(DeviceFiles::kLicenseStateReleasing))
|
||||
return RELEASE_KEY_REQUEST_ERROR;
|
||||
} else if (!usage_provider_session_token_.empty()) {
|
||||
if (usage_support_type_ == kUsageEntrySupport) {
|
||||
if (!UpdateUsageInfo())
|
||||
return RELEASE_USAGE_INFO_FAILED;
|
||||
}
|
||||
}
|
||||
return KEY_MESSAGE;
|
||||
}
|
||||
@@ -680,56 +540,12 @@ CdmResponseType CdmSession::ReleaseKey(const CdmKeyResponse& key_response) {
|
||||
license_parser_->HandleKeyUpdateResponse(false, key_response);
|
||||
if (sts != KEY_ADDED) return (sts == KEY_ERROR) ? RELEASE_KEY_ERROR : sts;
|
||||
|
||||
if (is_offline_ || has_provider_session_token()) {
|
||||
if (is_offline_ || !license_parser_->provider_session_token().empty()) {
|
||||
DeleteLicense();
|
||||
|
||||
if (usage_support_type_ == kUsageEntrySupport &&
|
||||
has_provider_session_token()) {
|
||||
sts = DeleteUsageEntry(usage_entry_number_);
|
||||
if (NO_ERROR != sts) return sts;
|
||||
}
|
||||
}
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
CdmResponseType CdmSession::DeleteUsageEntry(uint32_t usage_entry_number) {
|
||||
if (usage_support_type_ != kUsageEntrySupport) {
|
||||
LOGE("CdmSession::DeleteUsageEntry: Unexpected usage type supported: %d",
|
||||
usage_support_type_);
|
||||
return INCORRECT_USAGE_SUPPORT_TYPE_1;
|
||||
}
|
||||
|
||||
// The usage entry cannot be deleted if it has a crypto session handling
|
||||
// it, so close and reopen session.
|
||||
CdmResponseType sts;
|
||||
crypto_session_->Close();
|
||||
crypto_session_.reset(new CryptoSession(crypto_metrics_));
|
||||
M_TIME(
|
||||
sts = crypto_session_->Open(requested_security_level_),
|
||||
crypto_metrics_,
|
||||
crypto_session_open_,
|
||||
sts,
|
||||
requested_security_level_);
|
||||
if (sts != NO_ERROR) return sts;
|
||||
|
||||
usage_table_header_ = NULL;
|
||||
if (crypto_session_->GetUsageSupportType(&usage_support_type_) == NO_ERROR) {
|
||||
if (usage_support_type_ == kUsageEntrySupport)
|
||||
usage_table_header_ = crypto_session_->GetUsageTableHeader();
|
||||
} else {
|
||||
usage_support_type_ = kNonSecureUsageSupport;
|
||||
}
|
||||
|
||||
if (usage_table_header_ == NULL) {
|
||||
LOGE("CdmSession::DeleteUsageEntry: Usage table header unavailable");
|
||||
return INCORRECT_USAGE_SUPPORT_TYPE_1;
|
||||
}
|
||||
|
||||
return usage_table_header_->DeleteEntry(usage_entry_number,
|
||||
file_handle_.get(),
|
||||
crypto_metrics_);
|
||||
}
|
||||
|
||||
bool CdmSession::IsKeyLoaded(const KeyId& key_id) {
|
||||
return license_parser_->IsKeyLoaded(key_id);
|
||||
}
|
||||
@@ -803,11 +619,10 @@ CdmResponseType CdmSession::StoreLicense() {
|
||||
|
||||
std::string app_id;
|
||||
GetApplicationId(&app_id);
|
||||
std::string usage_entry;
|
||||
if (!file_handle_->StoreUsageInfo(provider_session_token, key_request_,
|
||||
key_response_,
|
||||
DeviceFiles::GetUsageInfoFileName(app_id),
|
||||
key_set_id_, usage_entry_,
|
||||
usage_entry_number_)) {
|
||||
key_response_, app_id, key_set_id_,
|
||||
usage_entry)) {
|
||||
LOGE("CdmSession::StoreLicense: Unable to store usage info");
|
||||
return STORE_USAGE_INFO_ERROR;
|
||||
}
|
||||
@@ -815,13 +630,13 @@ CdmResponseType CdmSession::StoreLicense() {
|
||||
}
|
||||
|
||||
bool CdmSession::StoreLicense(DeviceFiles::LicenseState state) {
|
||||
std::string usage_entry;
|
||||
return file_handle_->StoreLicense(
|
||||
key_set_id_, state, offline_init_data_, key_request_, key_response_,
|
||||
offline_key_renewal_request_, offline_key_renewal_response_,
|
||||
offline_release_server_url_, policy_engine_->GetPlaybackStartTime(),
|
||||
policy_engine_->GetLastPlaybackTime(),
|
||||
policy_engine_->GetGracePeriodEndTime(), app_parameters_, usage_entry_,
|
||||
usage_entry_number_);
|
||||
policy_engine_->GetGracePeriodEndTime(), app_parameters_, usage_entry);
|
||||
}
|
||||
|
||||
CdmResponseType CdmSession::ReleaseCrypto() {
|
||||
@@ -830,7 +645,7 @@ CdmResponseType CdmSession::ReleaseCrypto() {
|
||||
}
|
||||
|
||||
bool CdmSession::DeleteLicense() {
|
||||
if (!is_offline_ && !has_provider_session_token())
|
||||
if (!is_offline_ && license_parser_->provider_session_token().empty())
|
||||
return false;
|
||||
|
||||
if (is_offline_) {
|
||||
@@ -839,8 +654,7 @@ bool CdmSession::DeleteLicense() {
|
||||
std::string app_id;
|
||||
GetApplicationId(&app_id);
|
||||
return file_handle_->DeleteUsageInfo(
|
||||
DeviceFiles::GetUsageInfoFileName(app_id),
|
||||
license_parser_->provider_session_token());
|
||||
app_id, license_parser_->provider_session_token());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -873,48 +687,24 @@ void CdmSession::GetApplicationId(std::string* app_id) {
|
||||
|
||||
CdmResponseType CdmSession::DeleteMultipleUsageInformation(
|
||||
const std::vector<std::string>& provider_session_tokens) {
|
||||
CdmResponseType sts = crypto_session_->DeleteMultipleUsageInformation(
|
||||
provider_session_tokens);
|
||||
crypto_metrics_->crypto_session_delete_multiple_usage_information_
|
||||
.Increment(sts);
|
||||
return sts;
|
||||
}
|
||||
|
||||
CdmResponseType CdmSession::UpdateUsageTableInformation() {
|
||||
CdmResponseType sts;
|
||||
M_TIME(
|
||||
sts = crypto_session_->UpdateUsageInformation(),
|
||||
crypto_metrics_,
|
||||
crypto_session_update_usage_information_,
|
||||
sts = crypto_session_->DeleteMultipleUsageInformation(
|
||||
provider_session_tokens),
|
||||
&metrics_,
|
||||
crypto_session_delete_multiple_usage_information_,
|
||||
sts);
|
||||
return sts;
|
||||
}
|
||||
|
||||
CdmResponseType CdmSession::UpdateUsageEntryInformation() {
|
||||
if (usage_support_type_ != kUsageEntrySupport ||
|
||||
!has_provider_session_token() ||
|
||||
usage_table_header_ == nullptr) {
|
||||
LOGE("CdmSession::UpdateUsageEntryInformation: Unexpected state, "
|
||||
"usage support type: %d, PST present: %s, usage table header available"
|
||||
": %s", usage_support_type_,
|
||||
has_provider_session_token() ? "yes" : "no",
|
||||
usage_table_header_ == nullptr ? "no" : "yes");
|
||||
return INCORRECT_USAGE_SUPPORT_TYPE_2;
|
||||
}
|
||||
|
||||
CdmResponseType sts = usage_table_header_->UpdateEntry(crypto_session_.get(),
|
||||
&usage_entry_);
|
||||
|
||||
if (sts != NO_ERROR) return sts;
|
||||
|
||||
if (is_offline_)
|
||||
StoreLicense(is_release_
|
||||
? DeviceFiles::kLicenseStateReleasing
|
||||
: DeviceFiles::kLicenseStateActive);
|
||||
else if (!usage_provider_session_token_.empty())
|
||||
UpdateUsageInfo();
|
||||
|
||||
return NO_ERROR;
|
||||
CdmResponseType CdmSession::UpdateUsageInformation() {
|
||||
CdmResponseType sts;
|
||||
M_TIME(
|
||||
sts = crypto_session_->UpdateUsageInformation(),
|
||||
&metrics_,
|
||||
crypto_session_update_usage_information_,
|
||||
sts);
|
||||
return sts;
|
||||
}
|
||||
|
||||
CdmResponseType CdmSession::GenericEncrypt(const std::string& in_buffer,
|
||||
@@ -934,7 +724,7 @@ CdmResponseType CdmSession::GenericEncrypt(const std::string& in_buffer,
|
||||
iv,
|
||||
algorithm,
|
||||
out_buffer),
|
||||
crypto_metrics_,
|
||||
&metrics_,
|
||||
crypto_session_generic_encrypt_,
|
||||
sts,
|
||||
metrics::Pow2Bucket(in_buffer.size()),
|
||||
@@ -959,7 +749,7 @@ CdmResponseType CdmSession::GenericDecrypt(const std::string& in_buffer,
|
||||
iv,
|
||||
algorithm,
|
||||
out_buffer),
|
||||
crypto_metrics_,
|
||||
&metrics_,
|
||||
crypto_session_generic_decrypt_,
|
||||
sts,
|
||||
metrics::Pow2Bucket(in_buffer.size()),
|
||||
@@ -982,7 +772,7 @@ CdmResponseType CdmSession::GenericSign(const std::string& message,
|
||||
key_id,
|
||||
algorithm,
|
||||
signature),
|
||||
crypto_metrics_,
|
||||
&metrics_,
|
||||
crypto_session_generic_sign_,
|
||||
sts,
|
||||
metrics::Pow2Bucket(message.size()),
|
||||
@@ -1001,7 +791,7 @@ CdmResponseType CdmSession::GenericVerify(const std::string& message,
|
||||
key_id,
|
||||
algorithm,
|
||||
signature),
|
||||
crypto_metrics_,
|
||||
&metrics_,
|
||||
crypto_session_generic_verify_,
|
||||
sts,
|
||||
metrics::Pow2Bucket(message.size()),
|
||||
@@ -1009,24 +799,6 @@ CdmResponseType CdmSession::GenericVerify(const std::string& message,
|
||||
return sts;
|
||||
}
|
||||
|
||||
bool CdmSession::UpdateUsageInfo() {
|
||||
std::string app_id;
|
||||
GetApplicationId(&app_id);
|
||||
|
||||
DeviceFiles::CdmUsageData usage_data;
|
||||
usage_data.provider_session_token = usage_provider_session_token_;
|
||||
usage_data.license_request = key_request_;
|
||||
usage_data.license = key_response_;
|
||||
usage_data.key_set_id = key_set_id_;
|
||||
usage_data.usage_entry = usage_entry_;
|
||||
usage_data.usage_entry_number = usage_entry_number_;
|
||||
|
||||
return file_handle_->UpdateUsageInfo(
|
||||
DeviceFiles::GetUsageInfoFileName(app_id),
|
||||
usage_provider_session_token_,
|
||||
usage_data);
|
||||
}
|
||||
|
||||
// For testing only - takes ownership of pointers
|
||||
|
||||
void CdmSession::set_license_parser(CdmLicense* license_parser) {
|
||||
|
||||
@@ -19,45 +19,71 @@ const std::string kProvisioningServerUrl =
|
||||
"https://www.googleapis.com/"
|
||||
"certificateprovisioning/v1/devicecertificates/create"
|
||||
"?key=AIzaSyB-5OLKTx2iU5mko18DfdwK5611JIjbUhE";
|
||||
|
||||
/*
|
||||
* Provisioning response is a base64-encoded protobuf, optionally within a
|
||||
* JSON wrapper. If the JSON wrapper is present, extract the embedded response
|
||||
* message. Then perform the base64 decode and return the result.
|
||||
*
|
||||
* If an error occurs during the parse or the decode, return an empty string.
|
||||
*/
|
||||
void ExtractAndDecodeSignedMessage(const std::string& provisioning_response,
|
||||
std::string* result) {
|
||||
const std::string json_start_substr("\"signedResponse\": \"");
|
||||
const std::string json_end_substr("\"");
|
||||
std::string message_string;
|
||||
|
||||
size_t start = provisioning_response.find(json_start_substr);
|
||||
|
||||
if (start == provisioning_response.npos) {
|
||||
// Message is not properly wrapped - reject it.
|
||||
LOGE("ExtractAndDecodeSignedMessage: cannot locate start substring");
|
||||
result->clear();
|
||||
return;
|
||||
} else {
|
||||
// Appears to be JSON-wrapped protobuf - find end of protobuf portion.
|
||||
size_t end = provisioning_response.find(json_end_substr,
|
||||
start + json_start_substr.length());
|
||||
if (end == provisioning_response.npos) {
|
||||
LOGE("ExtractAndDecodeSignedMessage: cannot locate end substring");
|
||||
result->clear();
|
||||
return;
|
||||
}
|
||||
size_t b64_string_size = end - start - json_start_substr.length();
|
||||
message_string.assign(provisioning_response,
|
||||
start + json_start_substr.length(), b64_string_size);
|
||||
}
|
||||
|
||||
if (message_string.empty()) {
|
||||
LOGE("ExtractAndDecodeSignedMessage: CdmProvisioningResponse is empty");
|
||||
result->clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// Decode the base64-encoded message.
|
||||
const std::vector<uint8_t> decoded_message =
|
||||
wvcdm::Base64SafeDecode(message_string);
|
||||
result->assign(decoded_message.begin(), decoded_message.end());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace wvcdm {
|
||||
// Protobuf generated classes.
|
||||
using video_widevine::ClientIdentification;
|
||||
using video_widevine::EncryptedClientIdentification;
|
||||
using video_widevine::ProvisioningOptions;
|
||||
using video_widevine::ProvisioningRequest;
|
||||
using video_widevine::ProvisioningResponse;
|
||||
using video_widevine::SignedProvisioningMessage;
|
||||
|
||||
/*
|
||||
* This function converts SignedProvisioningRequest into base64 string. It then
|
||||
* wraps it in JSON format expected by the frontend. This server requires a
|
||||
* "web-safe" base 64 encoding, where '+' becomes '-' and '/' becomes '_'.
|
||||
*
|
||||
* Returns the JSON formated string in *request. The JSON string will be
|
||||
* appended as a query parameter, i.e. signedRequest=<base 64 encoded
|
||||
* SignedProvisioningRequest>. All base64 '=' padding chars must be removed.
|
||||
*
|
||||
* The JSON formated request takes the following format:
|
||||
*
|
||||
* base64 encoded message
|
||||
*/
|
||||
void CertificateProvisioning::ComposeJsonRequestAsQueryString(
|
||||
const std::string& message, CdmProvisioningRequest* request) {
|
||||
// Performs base64 encoding for message
|
||||
std::vector<uint8_t> message_vector(message.begin(), message.end());
|
||||
std::string message_b64 = Base64SafeEncodeNoPad(message_vector);
|
||||
request->assign(message_b64);
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the ClientIdentification message token type for provisioning request.
|
||||
* NOTE: a DRM Cert should never be presented to the provisioning server.
|
||||
*/
|
||||
bool CertificateProvisioning::GetProvisioningTokenType(
|
||||
ClientIdentification::TokenType* token_type) {
|
||||
CdmClientTokenType token = crypto_session_.GetPreProvisionTokenType();
|
||||
switch (token) {
|
||||
switch (crypto_session_.GetPreProvisionTokenType()) {
|
||||
case kClientTokenKeybox:
|
||||
*token_type = ClientIdentification::KEYBOX;
|
||||
return true;
|
||||
@@ -67,8 +93,6 @@ bool CertificateProvisioning::GetProvisioningTokenType(
|
||||
case kClientTokenDrmCert:
|
||||
default:
|
||||
// shouldn't happen
|
||||
LOGE("CertificateProvisioning::GetProvisioningTokenType: unexpected "
|
||||
"provisioning type: %d", token);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -83,7 +107,7 @@ bool CertificateProvisioning::SetSpoidParameter(
|
||||
const std::string& origin, const std::string& spoid,
|
||||
ProvisioningRequest* request) {
|
||||
if (!request) {
|
||||
LOGE("CertificateProvisioning::SetSpoidParameter: No request buffer "
|
||||
LOGE("CertificateProvisioning::SetSpoidParameter : No request buffer "
|
||||
"passed to method.");
|
||||
return false;
|
||||
}
|
||||
@@ -102,7 +126,8 @@ bool CertificateProvisioning::SetSpoidParameter(
|
||||
// Legacy behavior - Concatenate Unique ID with Origin
|
||||
std::string device_unique_id;
|
||||
if (!crypto_session_.GetInternalDeviceUniqueId(&device_unique_id)) {
|
||||
LOGE("CryptoSession::GetStableIdField: Failure to get device unique ID");
|
||||
LOGE("CertificateProvisioning::SetSpoidParameter: Failure getting "
|
||||
"device unique ID");
|
||||
return false;
|
||||
}
|
||||
request->set_stable_id(device_unique_id + origin);
|
||||
@@ -123,9 +148,9 @@ SignedProvisioningMessage::ProtocolVersion
|
||||
}
|
||||
|
||||
/*
|
||||
* Composes a device provisioning request and output the request in JSON format
|
||||
* in *request. It also returns the default url for the provisioning server
|
||||
* in *default_url.
|
||||
* Compose a device provisioning request and output *request in a
|
||||
* JSON-compatible format (web-safe base64).
|
||||
* Also return *default_url of the provisioning server.
|
||||
*
|
||||
* Returns NO_ERROR for success and CERT_PROVISIONING_REQUEST_ERROR_? if fails.
|
||||
*/
|
||||
@@ -164,6 +189,22 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequest(
|
||||
client_id->set_token(token);
|
||||
client_id->set_type(token_type);
|
||||
|
||||
#if 0 // TODO(gmorgan) Encrypt ClientIdentification. Pending Design.
|
||||
if (service_certificate_->has_certificate()) {
|
||||
EncryptedClientIdentification* encrypted_client_id =
|
||||
provisioning_request.mutable_encrypted_client_id();
|
||||
CdmResponseType status;
|
||||
status = service_certificate_->EncryptClientId(&crypto_session_, client_id,
|
||||
encrypted_client_id);
|
||||
if (status == NO_ERROR) {
|
||||
provisioning_request.clear_client_id();
|
||||
} else {
|
||||
provisioning_request.clear_encrypted_client_id();
|
||||
}
|
||||
return status;
|
||||
}
|
||||
#endif
|
||||
|
||||
uint32_t nonce;
|
||||
if (!crypto_session_.GenerateNonce(&nonce)) {
|
||||
LOGE("GetProvisioningRequest: fails to generate a nonce");
|
||||
@@ -220,43 +261,13 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequest(
|
||||
std::string serialized_request;
|
||||
signed_provisioning_msg.SerializeToString(&serialized_request);
|
||||
|
||||
// Converts request into JSON string
|
||||
ComposeJsonRequestAsQueryString(serialized_request, request);
|
||||
// Return request as web-safe base64 string
|
||||
std::vector<uint8_t> request_vector(serialized_request.begin(),
|
||||
serialized_request.end());
|
||||
request->assign(Base64SafeEncodeNoPad(request_vector));
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parses the input json_str and locates substring using start_substr and
|
||||
* end_stubstr. The found base64 substring is then decoded and returns
|
||||
* in *result.
|
||||
*
|
||||
* Returns true for success and false if fails.
|
||||
*/
|
||||
bool CertificateProvisioning::ParseJsonResponse(
|
||||
const CdmProvisioningResponse& json_str, const std::string& start_substr,
|
||||
const std::string& end_substr, std::string* result) {
|
||||
std::string b64_string;
|
||||
size_t start = json_str.find(start_substr);
|
||||
if (start == json_str.npos) {
|
||||
LOGE("ParseJsonResponse: cannot find start substring");
|
||||
return false;
|
||||
}
|
||||
size_t end = json_str.find(end_substr, start + start_substr.length());
|
||||
if (end == json_str.npos) {
|
||||
LOGE("ParseJsonResponse cannot locate end substring");
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t b64_string_size = end - start - start_substr.length();
|
||||
b64_string.assign(json_str, start + start_substr.length(), b64_string_size);
|
||||
|
||||
// Decodes base64 substring and returns it in *result
|
||||
std::vector<uint8_t> result_vector = Base64SafeDecode(b64_string);
|
||||
result->assign(result_vector.begin(), result_vector.end());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* The response message consists of a device certificate and the device RSA key.
|
||||
* The device RSA key is stored in the T.E.E. The device certificate is stored
|
||||
@@ -267,13 +278,15 @@ bool CertificateProvisioning::ParseJsonResponse(
|
||||
CdmResponseType CertificateProvisioning::HandleProvisioningResponse(
|
||||
FileSystem* file_system, const CdmProvisioningResponse& response,
|
||||
std::string* cert, std::string* wrapped_key) {
|
||||
// Extracts signed response from JSON string, decodes base64 signed response
|
||||
const std::string kMessageStart = "\"signedResponse\": \"";
|
||||
const std::string kMessageEnd = "\"";
|
||||
std::string serialized_signed_response;
|
||||
if (!ParseJsonResponse(response, kMessageStart, kMessageEnd,
|
||||
&serialized_signed_response)) {
|
||||
LOGE("Fails to extract signed serialized response from JSON response");
|
||||
|
||||
std::string raw_string;
|
||||
// The response is base64 encoded in a JSON wrapper.
|
||||
// Extract it and decode it. If errors, return an empty string.
|
||||
ExtractAndDecodeSignedMessage(response, &raw_string);
|
||||
|
||||
if (raw_string.empty()) {
|
||||
LOGE("HandleProvisioningResponse: response message is empty or "
|
||||
"an invalid JSON/base64 string.");
|
||||
return CERT_PROVISIONING_RESPONSE_ERROR_1;
|
||||
}
|
||||
|
||||
@@ -281,7 +294,7 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse(
|
||||
// the provisioing request's input). Validate provisioning response and
|
||||
// stores private device RSA key and certificate.
|
||||
SignedProvisioningMessage signed_response;
|
||||
if (!signed_response.ParseFromString(serialized_signed_response)) {
|
||||
if (!signed_response.ParseFromString(raw_string)) {
|
||||
LOGE("HandleProvisioningResponse: fails to parse signed response");
|
||||
return CERT_PROVISIONING_RESPONSE_ERROR_2;
|
||||
}
|
||||
@@ -300,6 +313,7 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse(
|
||||
if (error) return CERT_PROVISIONING_RESPONSE_ERROR_3;
|
||||
|
||||
const std::string& signed_message = signed_response.message();
|
||||
const std::string& signature = signed_response.signature();
|
||||
ProvisioningResponse provisioning_response;
|
||||
|
||||
if (!provisioning_response.ParseFromString(signed_message)) {
|
||||
@@ -312,17 +326,28 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse(
|
||||
return CERT_PROVISIONING_RESPONSE_ERROR_5;
|
||||
}
|
||||
|
||||
const std::string& enc_rsa_key = provisioning_response.device_rsa_key();
|
||||
// If Provisioning 3.0 (OEM Cert provisioned), verify that the
|
||||
// message is properly signed.
|
||||
if (crypto_session_.GetPreProvisionTokenType() == kClientTokenOemCert) {
|
||||
if (!service_certificate_->VerifySignedMessage(signed_message, signature)) {
|
||||
LOGE("HandleProvisioningResponse: message not properly signed");
|
||||
return CERT_PROVISIONING_RESPONSE_ERROR_6;
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& new_private_key = provisioning_response.device_rsa_key();
|
||||
const std::string& nonce = provisioning_response.nonce();
|
||||
const std::string& rsa_key_iv = provisioning_response.device_rsa_key_iv();
|
||||
const std::string& iv = provisioning_response.device_rsa_key_iv();
|
||||
|
||||
const std::string& wrapping_key = (provisioning_response.has_wrapping_key()) ?
|
||||
provisioning_response.wrapping_key() : std::string();
|
||||
const std::string& signature = signed_response.signature();
|
||||
std::string wrapped_rsa_key;
|
||||
provisioning_response.wrapping_key() : std::string();
|
||||
|
||||
std::string wrapped_private_key;
|
||||
|
||||
if (!crypto_session_.RewrapCertificate(signed_message, signature, nonce,
|
||||
enc_rsa_key, rsa_key_iv, wrapping_key,
|
||||
&wrapped_rsa_key)) {
|
||||
LOGE("HandleProvisioningResponse: RewrapDeviceRSAKey fails");
|
||||
new_private_key, iv, wrapping_key,
|
||||
&wrapped_private_key)) {
|
||||
LOGE("HandleProvisioningResponse: RewrapCertificate fails");
|
||||
return CERT_PROVISIONING_RESPONSE_ERROR_6;
|
||||
}
|
||||
|
||||
@@ -330,10 +355,13 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse(
|
||||
|
||||
if (cert_type_ == kCertificateX509) {
|
||||
*cert = provisioning_response.device_certificate();
|
||||
*wrapped_key = wrapped_rsa_key;
|
||||
*wrapped_key = wrapped_private_key;
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
// This is the entire certificate (SignedDrmDeviceCertificate).
|
||||
// This will be stored to the device as the final step in the device
|
||||
// provisioning process.
|
||||
const std::string& device_certificate =
|
||||
provisioning_response.device_certificate();
|
||||
|
||||
@@ -342,7 +370,7 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse(
|
||||
LOGE("HandleProvisioningResponse: failed to init DeviceFiles");
|
||||
return CERT_PROVISIONING_RESPONSE_ERROR_7;
|
||||
}
|
||||
if (!handle.StoreCertificate(device_certificate, wrapped_rsa_key)) {
|
||||
if (!handle.StoreCertificate(device_certificate, wrapped_private_key)) {
|
||||
LOGE("HandleProvisioningResponse: failed to save provisioning certificate");
|
||||
return CERT_PROVISIONING_RESPONSE_ERROR_8;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -40,8 +40,6 @@ using video_widevine_client::sdk::
|
||||
UsageTableInfo_UsageEntryInfo_UsageEntryStorage_LICENSE;
|
||||
using video_widevine_client::sdk::
|
||||
UsageTableInfo_UsageEntryInfo_UsageEntryStorage_USAGE_INFO;
|
||||
using video_widevine_client::sdk::
|
||||
UsageTableInfo_UsageEntryInfo_UsageEntryStorage_UNKNOWN;
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -51,7 +49,7 @@ const char kUsageInfoFileNamePrefix[] = "usage";
|
||||
const char kUsageInfoFileNameExt[] = ".bin";
|
||||
const char kLicenseFileNameExt[] = ".lic";
|
||||
const char kEmptyFileName[] = "";
|
||||
const char kUsageTableFileName[] = "usgtable.bin";
|
||||
const char kUsageTableFileName[] = "usagetable.bin";
|
||||
const char kWildcard[] = "*";
|
||||
|
||||
bool Hash(const std::string& data, std::string* hash) {
|
||||
@@ -146,7 +144,6 @@ bool DeviceFiles::RetrieveCertificate(std::string* certificate,
|
||||
}
|
||||
|
||||
DeviceCertificate device_certificate = file.device_certificate();
|
||||
|
||||
*certificate = device_certificate.certificate();
|
||||
*wrapped_private_key = device_certificate.wrapped_private_key();
|
||||
return true;
|
||||
@@ -179,8 +176,7 @@ bool DeviceFiles::StoreLicense(
|
||||
const std::string& release_server_url, int64_t playback_start_time,
|
||||
int64_t last_playback_time, int64_t grace_period_end_time,
|
||||
const CdmAppParameterMap& app_parameters,
|
||||
const CdmUsageEntry& usage_entry,
|
||||
const uint32_t usage_entry_number) {
|
||||
const CdmUsageEntry& usage_entry) {
|
||||
if (!initialized_) {
|
||||
LOGW("DeviceFiles::StoreLicense: not initialized");
|
||||
return false;
|
||||
@@ -222,7 +218,6 @@ bool DeviceFiles::StoreLicense(
|
||||
app_params->set_value(iter->second);
|
||||
}
|
||||
license->set_usage_entry(usage_entry);
|
||||
license->set_usage_entry_number(usage_entry_number);
|
||||
|
||||
std::string serialized_file;
|
||||
file.SerializeToString(&serialized_file);
|
||||
@@ -237,8 +232,7 @@ bool DeviceFiles::RetrieveLicense(
|
||||
CdmKeyMessage* license_renewal_request, CdmKeyResponse* license_renewal,
|
||||
std::string* release_server_url, int64_t* playback_start_time,
|
||||
int64_t* last_playback_time, int64_t* grace_period_end_time,
|
||||
CdmAppParameterMap* app_parameters, CdmUsageEntry* usage_entry,
|
||||
uint32_t* usage_entry_number) {
|
||||
CdmAppParameterMap* app_parameters, CdmUsageEntry* usage_entry) {
|
||||
if (!initialized_) {
|
||||
LOGW("DeviceFiles::RetrieveLicense: not initialized");
|
||||
return false;
|
||||
@@ -293,7 +287,6 @@ bool DeviceFiles::RetrieveLicense(
|
||||
license.app_parameters(i).value();
|
||||
}
|
||||
*usage_entry = license.usage_entry();
|
||||
*usage_entry_number = license.usage_entry_number();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -354,7 +347,6 @@ bool DeviceFiles::DeleteAllFiles() {
|
||||
|
||||
// We pass an empty string to RemoveFile to delete the device files base
|
||||
// directory itself.
|
||||
// TODO[gmorgan]: verify RemoveFile("") should remove all files.
|
||||
return RemoveFile(kEmptyFileName);
|
||||
}
|
||||
|
||||
@@ -388,21 +380,21 @@ bool DeviceFiles::UnreserveLicenseId(const std::string& key_set_id) {
|
||||
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& app_id,
|
||||
const std::string& key_set_id,
|
||||
const std::string& usage_entry,
|
||||
uint32_t usage_entry_number) {
|
||||
const CdmUsageEntry& usage_entry) {
|
||||
if (!initialized_) {
|
||||
LOGW("DeviceFiles::StoreUsageInfo: not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
video_widevine_client::sdk::File file;
|
||||
if (!FileExists(usage_info_file_name)) {
|
||||
std::string file_name = GetUsageInfoFileName(app_id);
|
||||
if (!FileExists(file_name)) {
|
||||
file.set_type(video_widevine_client::sdk::File::USAGE_INFO);
|
||||
file.set_version(video_widevine_client::sdk::File::VERSION_1);
|
||||
} else {
|
||||
if (!RetrieveHashedFile(usage_info_file_name, &file)) {
|
||||
if (!RetrieveHashedFile(file_name, &file)) {
|
||||
LOGW("DeviceFiles::StoreUsageInfo: Unable to parse file");
|
||||
return false;
|
||||
}
|
||||
@@ -417,21 +409,92 @@ bool DeviceFiles::StoreUsageInfo(const std::string& provider_session_token,
|
||||
provider_session->set_license(key_response.data(), key_response.size());
|
||||
provider_session->set_key_set_id(key_set_id.data(), key_set_id.size());
|
||||
provider_session->set_usage_entry(usage_entry);
|
||||
provider_session->set_usage_entry_number(usage_entry_number);
|
||||
|
||||
std::string serialized_file;
|
||||
file.SerializeToString(&serialized_file);
|
||||
return StoreFileWithHash(usage_info_file_name, serialized_file);
|
||||
return StoreFileWithHash(file_name, serialized_file);
|
||||
}
|
||||
|
||||
bool DeviceFiles::DeleteUsageInfo(const std::string& usage_info_file_name,
|
||||
bool DeviceFiles::ListUsageRecords(const std::string& app_id,
|
||||
std::vector<std::string>* ksids) {
|
||||
if (!initialized_) {
|
||||
LOGW("DeviceFiles::ListUsageRecords: not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ksids == NULL) {
|
||||
LOGW("DeviceFiles::ListUsageRecords: return parameter 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) {
|
||||
ksids->clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
video_widevine_client::sdk::File file;
|
||||
if (!RetrieveHashedFile(file_name, &file)) {
|
||||
LOGW("DeviceFiles::ListUsageRecords: Unable to parse file");
|
||||
return false;
|
||||
}
|
||||
|
||||
ksids->clear();
|
||||
|
||||
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().empty()) {
|
||||
ksids->push_back(file.usage_info().sessions(i).key_set_id());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeviceFiles::GetProviderSessionToken(const std::string& app_id,
|
||||
const std::string& key_set_id,
|
||||
std::string* provider_session_token) {
|
||||
if (!initialized_) {
|
||||
LOGW("DeviceFiles::GetProviderSessionToken: not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (provider_session_token == NULL) {
|
||||
LOGW("DeviceFiles::GetProviderSessionToken: NULL return argument pointer");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string file_name = GetUsageInfoFileName(app_id);
|
||||
if (!FileExists(file_name) || GetFileSize(file_name) == 0) {
|
||||
LOGW("DeviceFiles::GetProviderSessionToken: empty file");
|
||||
return false;
|
||||
}
|
||||
|
||||
video_widevine_client::sdk::File file;
|
||||
if (!RetrieveHashedFile(file_name, &file)) {
|
||||
LOGW("DeviceFiles::GetProviderSessionToken: unable to parse 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& app_id,
|
||||
const std::string& provider_session_token) {
|
||||
if (!initialized_) {
|
||||
LOGW("DeviceFiles::DeleteUsageInfo: not initialized");
|
||||
return false;
|
||||
}
|
||||
video_widevine_client::sdk::File file;
|
||||
if (!RetrieveHashedFile(usage_info_file_name, &file)) return false;
|
||||
std::string file_name = GetUsageInfoFileName(app_id);
|
||||
if (!RetrieveHashedFile(file_name, &file)) return false;
|
||||
|
||||
UsageInfo* usage_info = file.mutable_usage_info();
|
||||
int index = 0;
|
||||
@@ -460,11 +523,11 @@ bool DeviceFiles::DeleteUsageInfo(const std::string& usage_info_file_name,
|
||||
|
||||
std::string serialized_file;
|
||||
file.SerializeToString(&serialized_file);
|
||||
return StoreFileWithHash(usage_info_file_name, serialized_file);
|
||||
return StoreFileWithHash(file_name, serialized_file);
|
||||
}
|
||||
|
||||
bool DeviceFiles::DeleteAllUsageInfoForApp(
|
||||
const std::string& usage_info_file_name,
|
||||
const std::string& app_id,
|
||||
std::vector<std::string>* provider_session_tokens) {
|
||||
if (!initialized_) {
|
||||
LOGW("DeviceFiles::DeleteAllUsageInfoForApp: not initialized");
|
||||
@@ -476,21 +539,22 @@ bool DeviceFiles::DeleteAllUsageInfoForApp(
|
||||
}
|
||||
provider_session_tokens->clear();
|
||||
|
||||
if (!FileExists(usage_info_file_name)) return true;
|
||||
std::string file_name = GetUsageInfoFileName(app_id);
|
||||
if (!FileExists(file_name)) return true;
|
||||
|
||||
video_widevine_client::sdk::File file;
|
||||
if (RetrieveHashedFile(usage_info_file_name, &file)) {
|
||||
if (RetrieveHashedFile(file_name, &file)) {
|
||||
for (int i = 0; i < file.usage_info().sessions_size(); ++i) {
|
||||
provider_session_tokens->push_back(file.usage_info().sessions(i).token());
|
||||
}
|
||||
} else {
|
||||
LOGW("DeviceFiles::DeleteAllUsageInfoForApp: Unable to retrieve file");
|
||||
}
|
||||
return RemoveFile(usage_info_file_name);
|
||||
return RemoveFile(file_name);
|
||||
}
|
||||
|
||||
bool DeviceFiles::RetrieveUsageInfo(
|
||||
const std::string& usage_info_file_name,
|
||||
const std::string& app_id,
|
||||
std::vector<std::pair<CdmKeyMessage, CdmKeyResponse> >* usage_info) {
|
||||
if (!initialized_) {
|
||||
LOGW("DeviceFiles::RetrieveUsageInfo: not initialized");
|
||||
@@ -504,14 +568,14 @@ bool DeviceFiles::RetrieveUsageInfo(
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!FileExists(usage_info_file_name) ||
|
||||
GetFileSize(usage_info_file_name) == 0) {
|
||||
std::string file_name = GetUsageInfoFileName(app_id);
|
||||
if (!FileExists(file_name) || GetFileSize(file_name) == 0) {
|
||||
usage_info->resize(0);
|
||||
return true;
|
||||
}
|
||||
|
||||
video_widevine_client::sdk::File file;
|
||||
if (!RetrieveHashedFile(usage_info_file_name, &file)) {
|
||||
if (!RetrieveHashedFile(file_name, &file)) {
|
||||
LOGW("DeviceFiles::RetrieveUsageInfo: Unable to parse file");
|
||||
return false;
|
||||
}
|
||||
@@ -526,19 +590,19 @@ bool DeviceFiles::RetrieveUsageInfo(
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeviceFiles::RetrieveUsageInfo(const std::string& usage_info_file_name,
|
||||
bool DeviceFiles::RetrieveUsageInfo(const std::string& app_id,
|
||||
const std::string& provider_session_token,
|
||||
CdmKeyMessage* license_request,
|
||||
CdmKeyResponse* license_response,
|
||||
std::string* usage_entry,
|
||||
uint32_t* usage_entry_number) {
|
||||
CdmUsageEntry* usage_entry) {
|
||||
if (!initialized_) {
|
||||
LOGW("DeviceFiles::RetrieveUsageInfo: not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string file_name = GetUsageInfoFileName(app_id);
|
||||
video_widevine_client::sdk::File file;
|
||||
if (!RetrieveHashedFile(usage_info_file_name, &file)) {
|
||||
if (!RetrieveHashedFile(file_name, &file)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -548,8 +612,6 @@ bool DeviceFiles::RetrieveUsageInfo(const std::string& usage_info_file_name,
|
||||
*license_request = file.usage_info().sessions(index).license_request();
|
||||
*license_response = file.usage_info().sessions(index).license();
|
||||
*usage_entry = file.usage_info().sessions(index).usage_entry();
|
||||
*usage_entry_number =
|
||||
file.usage_info().sessions(index).usage_entry_number();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -558,32 +620,28 @@ bool DeviceFiles::RetrieveUsageInfo(const std::string& usage_info_file_name,
|
||||
}
|
||||
|
||||
bool DeviceFiles::RetrieveUsageInfoByKeySetId(
|
||||
const std::string& usage_info_file_name,
|
||||
const std::string& app_id,
|
||||
const std::string& key_set_id,
|
||||
std::string* provider_session_token,
|
||||
CdmKeyMessage* license_request,
|
||||
CdmKeyResponse* license_response,
|
||||
std::string* usage_entry,
|
||||
uint32_t* usage_entry_number) {
|
||||
CdmUsageEntry* usage_entry) {
|
||||
if (!initialized_) {
|
||||
LOGW("DeviceFiles::RetrieveUsageInfoByKeySetId: not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string file_name = GetUsageInfoFileName(app_id);
|
||||
video_widevine_client::sdk::File file;
|
||||
if (!RetrieveHashedFile(usage_info_file_name, &file)) {
|
||||
if (!RetrieveHashedFile(file_name, &file)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
for (; index < file.usage_info().sessions_size(); ++index) {
|
||||
if (file.usage_info().sessions(index).key_set_id() == key_set_id) {
|
||||
*provider_session_token = file.usage_info().sessions(index).token();
|
||||
*license_request = file.usage_info().sessions(index).license_request();
|
||||
*license_response = file.usage_info().sessions(index).license();
|
||||
*usage_entry = file.usage_info().sessions(index).usage_entry();
|
||||
*usage_entry_number =
|
||||
file.usage_info().sessions(index).usage_entry_number();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -591,190 +649,6 @@ bool DeviceFiles::RetrieveUsageInfoByKeySetId(
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DeviceFiles::StoreUsageInfo(const std::string& usage_info_file_name,
|
||||
const std::vector<CdmUsageData>& usage_data) {
|
||||
if (!initialized_) {
|
||||
LOGW("DeviceFiles::StoreUsageInfo: not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
video_widevine_client::sdk::File file;
|
||||
file.set_type(video_widevine_client::sdk::File::USAGE_INFO);
|
||||
file.set_version(video_widevine_client::sdk::File::VERSION_1);
|
||||
|
||||
UsageInfo* usage_info = file.mutable_usage_info();
|
||||
for (size_t i = 0; i < usage_data.size(); ++i) {
|
||||
UsageInfo_ProviderSession* provider_session = usage_info->add_sessions();
|
||||
|
||||
provider_session->set_token(usage_data[i].provider_session_token.data(),
|
||||
usage_data[i].provider_session_token.size());
|
||||
provider_session->set_license_request(usage_data[i].license_request.data(),
|
||||
usage_data[i].license_request.size());
|
||||
provider_session->set_license(usage_data[i].license.data(),
|
||||
usage_data[i].license.size());
|
||||
provider_session->set_key_set_id(usage_data[i].key_set_id.data(),
|
||||
usage_data[i].key_set_id.size());
|
||||
provider_session->set_usage_entry(usage_data[i].usage_entry);
|
||||
provider_session->set_usage_entry_number(usage_data[i].usage_entry_number);
|
||||
}
|
||||
|
||||
std::string serialized_file;
|
||||
file.SerializeToString(&serialized_file);
|
||||
return StoreFileWithHash(usage_info_file_name, serialized_file);
|
||||
}
|
||||
|
||||
bool DeviceFiles::UpdateUsageInfo(const std::string& usage_info_file_name,
|
||||
const std::string& provider_session_token,
|
||||
const CdmUsageData& usage_data) {
|
||||
if (!initialized_) {
|
||||
LOGW("DeviceFiles::UpdateUsageInfo: not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
video_widevine_client::sdk::File file;
|
||||
if (!FileExists(usage_info_file_name)) {
|
||||
LOGW("DeviceFiles::UpdateUsageInfo: Usage file does not exist");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (!RetrieveHashedFile(usage_info_file_name, &file)) {
|
||||
LOGW("DeviceFiles::UpdateUsageInfo: Unable to parse file");
|
||||
return false;
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
for (; index < file.usage_info().sessions_size(); ++index) {
|
||||
if (file.usage_info().sessions(index).token() == provider_session_token) {
|
||||
UsageInfo* usage_info = file.mutable_usage_info();
|
||||
UsageInfo_ProviderSession* provider_session =
|
||||
usage_info->mutable_sessions(index);
|
||||
provider_session->set_license_request(usage_data.license_request);
|
||||
provider_session->set_license(usage_data.license);
|
||||
provider_session->set_key_set_id(usage_data.key_set_id);
|
||||
provider_session->set_usage_entry(usage_data.usage_entry);
|
||||
provider_session->set_usage_entry_number(usage_data.usage_entry_number);
|
||||
|
||||
std::string serialized_file;
|
||||
file.SerializeToString(&serialized_file);
|
||||
return StoreFileWithHash(usage_info_file_name, serialized_file);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DeviceFiles::RetrieveUsageInfo(const std::string& usage_info_file_name,
|
||||
std::vector<CdmUsageData>* usage_data) {
|
||||
if (!initialized_) {
|
||||
LOGW("DeviceFiles::RetrieveUsageInfo: not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (usage_data == NULL) {
|
||||
LOGW("DeviceFiles::RetrieveUsageInfo: usage_data not provided");
|
||||
return false;
|
||||
}
|
||||
|
||||
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)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
usage_data->resize(file.usage_info().sessions_size());
|
||||
for (int i = 0; i < file.usage_info().sessions_size(); ++i) {
|
||||
(*usage_data)[i].provider_session_token =
|
||||
file.usage_info().sessions(i).token();
|
||||
(*usage_data)[i].license_request =
|
||||
file.usage_info().sessions(i).license_request();
|
||||
(*usage_data)[i].license = file.usage_info().sessions(i).license();
|
||||
(*usage_data)[i].key_set_id = file.usage_info().sessions(i).key_set_id();
|
||||
(*usage_data)[i].usage_entry = file.usage_info().sessions(i).usage_entry();
|
||||
(*usage_data)[i].usage_entry_number =
|
||||
file.usage_info().sessions(i).usage_entry_number();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeviceFiles::RetrieveUsageInfo(const std::string& usage_info_file_name,
|
||||
const std::string& provider_session_token,
|
||||
CdmUsageData* usage_data) {
|
||||
if (!initialized_) {
|
||||
LOGW("DeviceFiles::RetrieveUsageInfo: not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (usage_data == NULL) {
|
||||
LOGW("DeviceFiles::RetrieveUsageInfo: usage_data not provided");
|
||||
return false;
|
||||
}
|
||||
|
||||
video_widevine_client::sdk::File file;
|
||||
if (!RetrieveHashedFile(usage_info_file_name, &file)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
for (; index < file.usage_info().sessions_size(); ++index) {
|
||||
if (file.usage_info().sessions(index).token() == provider_session_token) {
|
||||
usage_data->provider_session_token =
|
||||
file.usage_info().sessions(index).token();
|
||||
usage_data->license_request =
|
||||
file.usage_info().sessions(index).license_request();
|
||||
usage_data->license = file.usage_info().sessions(index).license();
|
||||
usage_data->key_set_id = file.usage_info().sessions(index).key_set_id();
|
||||
usage_data->usage_entry = file.usage_info().sessions(index).usage_entry();
|
||||
usage_data->usage_entry_number =
|
||||
file.usage_info().sessions(index).usage_entry_number();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DeviceFiles::ListUsageInfoFiles(
|
||||
std::vector<std::string>* usage_info_file_names) {
|
||||
if (!initialized_) {
|
||||
LOGW("DeviceFiles::ListUsageInfoFiles: not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (usage_info_file_names == NULL) {
|
||||
LOGW("DeviceFiles::ListUsageInfoFiles: usage_info_file_names not provided");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
@@ -874,7 +748,7 @@ bool DeviceFiles::DeleteHlsAttributes(const std::string& key_set_id) {
|
||||
|
||||
bool DeviceFiles::StoreUsageTableInfo(
|
||||
const CdmUsageTableHeader& usage_table_header,
|
||||
const std::vector<CdmUsageEntryInfo>& usage_entry_info) {
|
||||
const std::vector<DeviceFiles::UsageEntryInfo>& usage_entry_info) {
|
||||
if (!initialized_) {
|
||||
LOGW("DeviceFiles::StoreUsageTableHeader: not initialized");
|
||||
return false;
|
||||
@@ -891,23 +765,23 @@ bool DeviceFiles::StoreUsageTableInfo(
|
||||
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_key_set_id(usage_entry_info[i].key_set_id);
|
||||
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_provider_session_token(
|
||||
usage_entry_info[i].provider_session_token);
|
||||
info->set_app_id(usage_entry_info[i].app_id);
|
||||
break;
|
||||
case kStorageUnknown:
|
||||
default:
|
||||
info->set_storage(
|
||||
UsageTableInfo_UsageEntryInfo_UsageEntryStorage_UNKNOWN);
|
||||
break;
|
||||
LOGW("DeviceFiles::StoreUsageTableHeader: unknown storage type: %d",
|
||||
usage_entry_info[i].storage_type);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -919,7 +793,7 @@ bool DeviceFiles::StoreUsageTableInfo(
|
||||
|
||||
bool DeviceFiles::RetrieveUsageTableInfo(
|
||||
CdmUsageTableHeader* usage_table_header,
|
||||
std::vector<CdmUsageEntryInfo>* usage_entry_info) {
|
||||
std::vector<DeviceFiles::UsageEntryInfo>* usage_entry_info) {
|
||||
if (!initialized_) {
|
||||
LOGW("DeviceFiles::RetrieveUsageTableInfo: not initialized");
|
||||
return false;
|
||||
@@ -963,20 +837,21 @@ bool DeviceFiles::RetrieveUsageTableInfo(
|
||||
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].key_set_id = info.key_set_id();
|
||||
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].provider_session_token =
|
||||
info.provider_session_token();
|
||||
(*usage_entry_info)[i].app_id = info.app_id();
|
||||
break;
|
||||
case UsageTableInfo_UsageEntryInfo_UsageEntryStorage_UNKNOWN:
|
||||
default:
|
||||
(*usage_entry_info)[i].storage_type = kStorageUnknown;
|
||||
break;
|
||||
LOGW("DeviceFiles::RetrieveUsageTableInfo: Unknown storage type: %d",
|
||||
info.storage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,6 @@ message License {
|
||||
// ignored if there is no grace period.
|
||||
optional int64 grace_period_end_time = 11 [default = 0];
|
||||
optional bytes usage_entry = 12;
|
||||
optional int64 usage_entry_number = 13;
|
||||
}
|
||||
|
||||
message UsageInfo {
|
||||
@@ -54,7 +53,6 @@ message UsageInfo {
|
||||
optional bytes license = 3;
|
||||
optional bytes key_set_id = 4;
|
||||
optional bytes usage_entry = 5;
|
||||
optional int64 usage_entry_number = 6;
|
||||
}
|
||||
|
||||
repeated ProviderSession sessions = 1;
|
||||
@@ -74,12 +72,12 @@ message UsageTableInfo {
|
||||
enum UsageEntryStorage {
|
||||
LICENSE = 1;
|
||||
USAGE_INFO = 2;
|
||||
UNKNOWN = 3;
|
||||
}
|
||||
|
||||
optional UsageEntryStorage storage = 1;
|
||||
optional bytes key_set_id = 2;
|
||||
optional bytes usage_info_file_name = 3; // hash of the app_id
|
||||
optional bytes key_set_id = 2; // used if storage is LICENSE
|
||||
optional bytes provider_session_token = 3; // used if storage is USAGE_INFO
|
||||
optional bytes app_id = 4; // used if storage is USAGE_INFO
|
||||
}
|
||||
|
||||
optional bytes usage_table_header = 1;
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include <vector>
|
||||
|
||||
#include "clock.h"
|
||||
#include "cdm_session.h"
|
||||
#include "crypto_key.h"
|
||||
#include "crypto_session.h"
|
||||
#include "device_files.h"
|
||||
@@ -284,8 +283,7 @@ CdmResponseType CdmLicense::PrepareKeyRequest(
|
||||
|
||||
CdmResponseType CdmLicense::PrepareKeyUpdateRequest(
|
||||
bool is_renewal, const CdmAppParameterMap& app_parameters,
|
||||
CdmSession* cdm_session, CdmKeyMessage* signed_request,
|
||||
std::string* server_url) {
|
||||
CdmKeyMessage* signed_request, std::string* server_url) {
|
||||
if (!initialized_) {
|
||||
LOGE("CdmLicense::PrepareKeyUpdateRequest: not initialized");
|
||||
return LICENSE_PARSER_NOT_INITIALIZED_1;
|
||||
@@ -343,13 +341,6 @@ CdmResponseType CdmLicense::PrepareKeyUpdateRequest(
|
||||
if (NO_ERROR != status) return status;
|
||||
}
|
||||
|
||||
// TODO(rfrias): Refactor to avoid needing to call CdmSession
|
||||
if (cdm_session &&
|
||||
cdm_session->get_usage_support_type() == kUsageEntrySupport) {
|
||||
CdmResponseType status = cdm_session->UpdateUsageEntryInformation();
|
||||
if (NO_ERROR != status) return status;
|
||||
}
|
||||
|
||||
std::string usage_report;
|
||||
CdmResponseType status = crypto_session_->GenerateUsageReport(
|
||||
provider_session_token_, &usage_report, &usage_duration_status,
|
||||
@@ -624,8 +615,7 @@ bool CdmLicense::RestoreOfflineLicense(
|
||||
const CdmKeyMessage& license_request,
|
||||
const CdmKeyResponse& license_response,
|
||||
const CdmKeyResponse& license_renewal_response, int64_t playback_start_time,
|
||||
int64_t last_playback_time, int64_t grace_period_end_time,
|
||||
CdmSession* cdm_session) {
|
||||
int64_t last_playback_time, int64_t grace_period_end_time) {
|
||||
if (license_request.empty() || license_response.empty()) {
|
||||
LOGE(
|
||||
"CdmLicense::RestoreOfflineLicense: key_request or response empty: "
|
||||
@@ -667,16 +657,9 @@ bool CdmLicense::RestoreOfflineLicense(
|
||||
}
|
||||
|
||||
if (!provider_session_token_.empty()) {
|
||||
if (cdm_session &&
|
||||
cdm_session->get_usage_support_type() == kUsageEntrySupport) {
|
||||
CdmResponseType status = cdm_session->UpdateUsageEntryInformation();
|
||||
if (NO_ERROR != status) return false;
|
||||
}
|
||||
|
||||
std::string usage_report;
|
||||
CryptoSession::UsageDurationStatus usage_duration_status =
|
||||
CryptoSession::kUsageDurationsInvalid;
|
||||
|
||||
int64_t seconds_since_started, seconds_since_last_played;
|
||||
sts = crypto_session_->GenerateUsageReport(
|
||||
provider_session_token_, &usage_report, &usage_duration_status,
|
||||
@@ -805,43 +788,6 @@ bool CdmLicense::IsKeyLoaded(const KeyId& key_id) {
|
||||
return loaded_keys_.find(key_id) != loaded_keys_.end();
|
||||
}
|
||||
|
||||
bool CdmLicense::ExtractProviderSessionToken(
|
||||
const CdmKeyResponse& license_response,
|
||||
std::string* provider_session_token) {
|
||||
if (license_response.empty()) {
|
||||
LOGW("CdmLicense::ExtractProviderSessionToken: empty license response");
|
||||
return false;
|
||||
}
|
||||
|
||||
SignedMessage signed_response;
|
||||
if (!signed_response.ParseFromString(license_response)) {
|
||||
LOGW(
|
||||
"CdmLicense::ExtractProviderSessionToken: unable to parse signed "
|
||||
"license response");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (signed_response.type() != SignedMessage::LICENSE) {
|
||||
LOGW("CdmLicense::ExtractProviderSessionToken: unrecognized signed message "
|
||||
"type: %d", signed_response.type());
|
||||
return false;
|
||||
}
|
||||
|
||||
License license;
|
||||
if (!license.ParseFromString(signed_response.msg())) {
|
||||
LOGE("CdmLicense::ExtractProviderSessionToken: unable to parse license "
|
||||
"response");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (license.id().has_provider_session_token()) {
|
||||
*provider_session_token = license.id().provider_session_token();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
CdmResponseType CdmLicense::HandleKeyErrorResponse(
|
||||
const SignedMessage& signed_message) {
|
||||
LicenseError license_error;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <string>
|
||||
|
||||
#include "log.h"
|
||||
#include "wv_cdm_constants.h"
|
||||
|
||||
namespace {
|
||||
// License protocol aliases
|
||||
@@ -86,17 +87,14 @@ bool LicenseKeys::GetAllowedUsage(const KeyId& key_id,
|
||||
}
|
||||
}
|
||||
|
||||
bool LicenseKeys::ApplyStatusChange(
|
||||
const CdmKeyStatus* new_status, const uint32_t* new_resolution,
|
||||
const CryptoSession::HdcpCapability* new_hdcp_level,
|
||||
bool* new_usable_keys) {
|
||||
bool LicenseKeys::ApplyStatusChange(CdmKeyStatus new_status,
|
||||
bool* new_usable_keys) {
|
||||
bool keys_changed = false;
|
||||
bool newly_usable = false;
|
||||
*new_usable_keys = false;
|
||||
for (LicenseKeyStatusIterator it = keys_.begin(); it != keys_.end(); ++it) {
|
||||
bool usable;
|
||||
if (it->second->ApplyStatusChange(new_status, new_resolution,
|
||||
new_hdcp_level, &usable)) {
|
||||
if (it->second->ApplyStatusChange(new_status, &usable)) {
|
||||
newly_usable |= usable;
|
||||
keys_changed = true;
|
||||
}
|
||||
@@ -125,6 +123,13 @@ bool LicenseKeys::MeetsConstraints(const KeyId& key_id) {
|
||||
}
|
||||
}
|
||||
|
||||
void LicenseKeys::ApplyConstraints(
|
||||
uint32_t new_resolution, CryptoSession::HdcpCapability new_hdcp_level) {
|
||||
for (LicenseKeyStatusIterator i = keys_.begin(); i != keys_.end(); ++i) {
|
||||
i->second->ApplyConstraints(new_resolution, new_hdcp_level);
|
||||
}
|
||||
}
|
||||
|
||||
void LicenseKeys::SetFromLicense(
|
||||
const video_widevine::License& license) {
|
||||
this->Clear();
|
||||
@@ -141,9 +146,6 @@ void LicenseKeys::SetFromLicense(
|
||||
LicenseKeyStatus::LicenseKeyStatus(const KeyContainer& key) :
|
||||
is_content_key_(false),
|
||||
key_status_(kKeyStatusInternalError),
|
||||
resolution_(kNoResolution),
|
||||
hdcp_level_(HDCP_NONE),
|
||||
can_check_constraints_(false),
|
||||
meets_constraints_(true),
|
||||
default_hdcp_level_(HDCP_NONE) {
|
||||
|
||||
@@ -220,55 +222,26 @@ bool LicenseKeyStatus::GetAllowedUsage(CdmKeyAllowedUsage* allowed_usage) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LicenseKeyStatus::ApplyStatusChange(
|
||||
const CdmKeyStatus* maybe_new_status, const uint32_t* maybe_new_resolution,
|
||||
const CryptoSession::HdcpCapability* maybe_new_hdcp_level,
|
||||
bool* newly_usable) {
|
||||
*newly_usable = false;
|
||||
bool LicenseKeyStatus::ApplyStatusChange(CdmKeyStatus new_status,
|
||||
bool* new_usable_key) {
|
||||
*new_usable_key = false;
|
||||
if (!is_content_key_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Most of this function is various work to calculate an updated value for
|
||||
// the status. We start at the same value as the current status.
|
||||
CdmKeyStatus updated_status = key_status_;
|
||||
|
||||
// Account for the new status, if provided.
|
||||
if (maybe_new_status != NULL) {
|
||||
const CdmKeyStatus& new_status = *maybe_new_status;
|
||||
can_check_constraints_ = (new_status == kKeyStatusUsable);
|
||||
updated_status = new_status;
|
||||
}
|
||||
|
||||
// Account for the new resolution, if provided.
|
||||
if (maybe_new_resolution != NULL) {
|
||||
resolution_ = *maybe_new_resolution;
|
||||
}
|
||||
|
||||
// Account for the new HDCP level, if provided.
|
||||
if (maybe_new_hdcp_level != NULL) {
|
||||
hdcp_level_ = *maybe_new_hdcp_level;
|
||||
}
|
||||
|
||||
// If we can, check the current state against the constraints and update the
|
||||
// status if needed.
|
||||
if (can_check_constraints_) {
|
||||
ApplyConstraints();
|
||||
CdmKeyStatus updated_status = new_status;
|
||||
if (updated_status == kKeyStatusUsable) {
|
||||
if (!MeetsConstraints()) {
|
||||
updated_status = kKeyStatusOutputNotAllowed;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if any of that work changed the key status.
|
||||
if (key_status_ != updated_status) {
|
||||
key_status_ = updated_status;
|
||||
if (updated_status == kKeyStatusUsable) {
|
||||
*newly_usable = true;
|
||||
*new_usable_key = true;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the key has constraints, find the constraint that applies.
|
||||
@@ -278,16 +251,12 @@ bool LicenseKeyStatus::ApplyStatusChange(
|
||||
// If the key has no constraints, or if the constraint has no HDCP
|
||||
// requirement, use the key's default HDCP setting to check against the
|
||||
// device's current HDCP level.
|
||||
void LicenseKeyStatus::ApplyConstraints() {
|
||||
if (resolution_ == kNoResolution) {
|
||||
// Until a resolution has been detected, the constraints cannot be checked.
|
||||
meets_constraints_ = true;
|
||||
return;
|
||||
}
|
||||
void LicenseKeyStatus::ApplyConstraints(
|
||||
uint32_t video_pixels, CryptoSession::HdcpCapability new_hdcp_level) {
|
||||
|
||||
VideoResolutionConstraint* current_constraint = NULL;
|
||||
if (HasConstraints()) {
|
||||
current_constraint = GetConstraintForRes(resolution_, constraints_);
|
||||
if (HasConstraints() && video_pixels != HDCP_UNSPECIFIED_VIDEO_RESOLUTION) {
|
||||
current_constraint = GetConstraintForRes(video_pixels, constraints_);
|
||||
if (NULL == current_constraint) {
|
||||
meets_constraints_ = false;
|
||||
return;
|
||||
@@ -302,7 +271,7 @@ void LicenseKeyStatus::ApplyConstraints() {
|
||||
desired_hdcp_level = default_hdcp_level_;
|
||||
}
|
||||
|
||||
meets_constraints_ = (hdcp_level_ >= desired_hdcp_level);
|
||||
meets_constraints_ = (new_hdcp_level >= desired_hdcp_level);
|
||||
}
|
||||
|
||||
void LicenseKeyStatus::SetConstraints(const ConstraintList& constraints) {
|
||||
|
||||
@@ -29,7 +29,8 @@
|
||||
#include "level3.h"
|
||||
#include "lock.h"
|
||||
#include "log.h"
|
||||
#include "metrics_collections.h"
|
||||
#include "metrics_front_end.h"
|
||||
#include "metrics_group.h"
|
||||
#include "properties.h"
|
||||
#include "wv_cdm_constants.h"
|
||||
|
||||
@@ -40,8 +41,6 @@ using wvcdm::kLevel3;
|
||||
namespace {
|
||||
|
||||
static const size_t kMaxGenericEncryptChunkSize = 100*1024;
|
||||
const OEMCryptoResult kOemCryptoResultVendorSpecificError1 =
|
||||
static_cast<OEMCryptoResult>(10008);
|
||||
|
||||
typedef struct {
|
||||
const uint8_t* key_id;
|
||||
@@ -376,169 +375,6 @@ void clear_cache_function(void *page, size_t len) {
|
||||
#endif
|
||||
}
|
||||
|
||||
// The WatchDog looks after a worker thread that is trying to initialize L3.
|
||||
// Once in a rare while, the L3 init does not finish and eats up CPU cycles.
|
||||
// If that happens, the watchdog thread will give up and return an error.
|
||||
class WatchDog {
|
||||
public:
|
||||
// Created by main thread.
|
||||
WatchDog() {
|
||||
pthread_mutex_init(&mutex_, NULL);
|
||||
pthread_cond_init(&condition_, NULL);
|
||||
status_ = OEMCrypto_SUCCESS;
|
||||
gave_up_ = false;
|
||||
}
|
||||
|
||||
// Deleted by either thread.
|
||||
~WatchDog() {
|
||||
pthread_cond_destroy(&condition_);
|
||||
}
|
||||
|
||||
// Starts worker thread.
|
||||
void StartThread() {
|
||||
running_ = true;
|
||||
if(pthread_create(&thread_, NULL, RunWatchDog, this)) {
|
||||
LOGE("Could not create watch dog thread.");
|
||||
status_ = OEMCrypto_ERROR_INIT_FAILED;
|
||||
running_ = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Function called by new worker thread in pthread_create.
|
||||
static void *RunWatchDog(void *watcher) {
|
||||
WatchDog* dog = reinterpret_cast<WatchDog *>(watcher);
|
||||
dog->DoInit();
|
||||
dog->SignalDoneAndCleanUp();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Called by worker thread.
|
||||
void DoInit() {
|
||||
std::string base_path;
|
||||
wvcdm::Properties::GetDeviceFilesBasePath(wvcdm::kSecurityLevelL3,
|
||||
&base_path);
|
||||
status_ = Level3_Initialize(clear_cache_function,
|
||||
base_path.c_str());
|
||||
}
|
||||
|
||||
std::string FailureFilename() {
|
||||
std::string path;
|
||||
if (!wvcdm::Properties::GetDeviceFilesBasePath(wvcdm::kSecurityLevelL3,
|
||||
&path)) {
|
||||
LOGW("WatchDog::FailureFilename: Unable to get base path");
|
||||
return "/data/l3_failure_file";
|
||||
}
|
||||
path += "l3_failure_file";
|
||||
return path;
|
||||
}
|
||||
|
||||
// Check to see if the failure file was created before that last abort.
|
||||
void CheckForPreviousFailure(wvcdm::metrics::OemCryptoDynamicAdapterMetrics* metrics) {
|
||||
wvcdm::FileSystem file_system;
|
||||
std::string filename = FailureFilename();
|
||||
if (!file_system.Exists(filename)) return;
|
||||
wvcdm::File* file = file_system.Open(filename, file_system.kReadOnly);
|
||||
if (file) {
|
||||
uint32_t flag = 0;
|
||||
ssize_t size = sizeof(flag);
|
||||
ssize_t size_read = file->Read(reinterpret_cast<char*>(&flag), size);
|
||||
file->Close();
|
||||
file_system.Remove(filename);
|
||||
if (size == size_read && flag) {
|
||||
LOGE("Previous L3 Init failed.");
|
||||
if (metrics == nullptr) return;
|
||||
metrics->OemCryptoDynamicAdapterMetrics::SetInitializationMode(
|
||||
wvcdm::metrics::OEMCrypto_INITIALIZED_L3_INITIALIZATION_FAILED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save the failure file before we abort.
|
||||
void SaveFailureInformation() {
|
||||
wvcdm::FileSystem file_system;
|
||||
std::string filename = FailureFilename();
|
||||
LOGD("failure filename = %s", filename.c_str());
|
||||
wvcdm::File* file = file_system.Open(
|
||||
filename, file_system.kCreate | file_system.kTruncate);
|
||||
if (!file) {
|
||||
LOGE("Could not create file %s", filename.c_str());
|
||||
return;
|
||||
}
|
||||
uint32_t flag = 0x6261640a; // bad
|
||||
ssize_t size = sizeof(flag);
|
||||
ssize_t size_written = file->Write(reinterpret_cast<char*>(&flag), size);
|
||||
file->Close();
|
||||
if (size != size_written) {
|
||||
LOGE("Wrote %d bytes, not %d, to file %s", size_written, size,
|
||||
filename.c_str());
|
||||
} else {
|
||||
LOGE("I wrote %d to %s", size_written, filename.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// Called by worker thread after DoInit has finshed.
|
||||
void SignalDoneAndCleanUp() {
|
||||
pthread_mutex_lock(&mutex_);
|
||||
running_ = false;
|
||||
pthread_cond_signal(&condition_);
|
||||
// If the main thread gave up, it won't delete this, so we must.
|
||||
bool should_delete = gave_up_;
|
||||
pthread_mutex_unlock(&mutex_);
|
||||
// https://isocpp.org/wiki/faq/freestore-mgmt#delete-this
|
||||
if (should_delete) delete this;
|
||||
}
|
||||
|
||||
// Called by main thread to wait for worker thread.
|
||||
OEMCryptoResult WaitForStatusAndCleanUp() {
|
||||
pthread_mutex_lock(&mutex_);
|
||||
struct timespec time_to_giveup;
|
||||
clock_gettime(CLOCK_REALTIME, &time_to_giveup);
|
||||
time_to_giveup.tv_sec += 120; // wait 2 minutes.
|
||||
if (running_) {
|
||||
pthread_cond_timedwait(&condition_, &mutex_, &time_to_giveup);
|
||||
}
|
||||
if (running_) {
|
||||
gave_up_ = true;
|
||||
status_ = OEMCrypto_ERROR_INIT_FAILED;
|
||||
LOGE("XXX WATCH DOG ERROR XXX");
|
||||
// HACK: this normally just returns an error. However, we are using it
|
||||
// as a signal to dump debugging information.
|
||||
Level3_GetOEMPublicCertificate(0, nullptr, nullptr);
|
||||
SaveFailureInformation();
|
||||
// This tells the worker thread to clean up after itself. It is not
|
||||
// really needed since we are going to abort. However, if somebody
|
||||
// removes the "abort()" below, then this is needed.
|
||||
pthread_detach(thread_);
|
||||
// This is controversial. The argument for an abort here is that if we
|
||||
// do not abort, we will suck all the life out of the user's battery. By
|
||||
// saving information to the file system, above, we can still track
|
||||
// metrics.
|
||||
abort();
|
||||
}
|
||||
// If we gave up waiting for init thread, we should not delete the mutex
|
||||
// out from under it.
|
||||
bool should_delete = !gave_up_;
|
||||
OEMCryptoResult status = status_;
|
||||
pthread_mutex_unlock(&mutex_);
|
||||
if (should_delete) {
|
||||
pthread_join(thread_, NULL);
|
||||
delete this;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
OEMCryptoResult status() { return status_; }
|
||||
|
||||
private:
|
||||
OEMCryptoResult status_;
|
||||
pthread_t thread_;
|
||||
pthread_mutex_t mutex_;
|
||||
pthread_cond_t condition_;
|
||||
bool running_;
|
||||
bool gave_up_;
|
||||
};
|
||||
|
||||
struct LevelSession {
|
||||
FunctionPointers* fcn;
|
||||
OEMCrypto_SESSION session;
|
||||
@@ -584,36 +420,44 @@ class Adapter {
|
||||
OEMCryptoResult Initialize() {
|
||||
|
||||
/*
|
||||
* To avoid changing the function signature and function contract, use a
|
||||
* reference to a singleton object for the metrics collected from the
|
||||
* dynamic adapter.
|
||||
* To avoid changing the function signature and function contract - declare
|
||||
* a one-off metrics group to collect detailed information about how
|
||||
* oemcrypto was intialized.
|
||||
*/
|
||||
wvcdm::metrics::OemCryptoDynamicAdapterMetrics& metrics =
|
||||
wvcdm::metrics::GetDynamicAdapterMetricsInstance();
|
||||
wvcdm::metrics::MetricsGroup metrics;
|
||||
|
||||
level1_ = FunctionPointers(); // start with all null pointers.
|
||||
level3_ = FunctionPointers(); // start with all null pointers.
|
||||
LoadLevel3();
|
||||
WatchDog *watcher = new WatchDog();
|
||||
watcher->CheckForPreviousFailure(&metrics);
|
||||
watcher->StartThread();
|
||||
OEMCryptoResult result = watcher->WaitForStatusAndCleanUp();
|
||||
std::string base_path;
|
||||
wvcdm::Properties::GetDeviceFilesBasePath(wvcdm::kSecurityLevelL3,
|
||||
&base_path);
|
||||
OEMCryptoResult result = Level3_Initialize(clear_cache_function,
|
||||
base_path.c_str());
|
||||
if (Level3_IsInApp()) {
|
||||
metrics.SetInitializationMode(
|
||||
M_RECORD(
|
||||
&metrics,
|
||||
oemcrypto_initialization_mode_,
|
||||
NO_TIME,
|
||||
wvcdm::metrics::OEMCrypto_INITIALIZED_USING_IN_APP);
|
||||
return result;
|
||||
}
|
||||
if (force_level3()) {
|
||||
LOGW("Test code. User requested falling back to L3");
|
||||
metrics.SetInitializationMode(
|
||||
M_RECORD(
|
||||
&metrics,
|
||||
oemcrypto_initialization_mode_,
|
||||
NO_TIME,
|
||||
wvcdm::metrics::OEMCrypto_INITIALIZED_FORCING_L3);
|
||||
return result;
|
||||
}
|
||||
LOGI("L3 Initialized. Trying L1.");
|
||||
std::string library_name;
|
||||
if (!wvcdm::Properties::GetOEMCryptoPath(&library_name)) {
|
||||
LOGW("L1 library not specified. Falling back to L3");
|
||||
metrics.OemCryptoDynamicAdapterMetrics::SetInitializationMode(
|
||||
M_RECORD(
|
||||
&metrics,
|
||||
oemcrypto_initialization_mode_,
|
||||
NO_TIME,
|
||||
wvcdm::metrics::OEMCrypto_INITIALIZED_USING_L3_NO_L1_LIBRARY_PATH);
|
||||
return result;
|
||||
}
|
||||
@@ -621,11 +465,14 @@ class Adapter {
|
||||
if (level1_library_ == NULL) {
|
||||
LOGW("Could not load %s. Falling back to L3. %s", library_name.c_str(),
|
||||
dlerror());
|
||||
metrics.OemCryptoDynamicAdapterMetrics::SetInitializationMode(
|
||||
M_RECORD(
|
||||
&metrics,
|
||||
oemcrypto_initialization_mode_,
|
||||
NO_TIME,
|
||||
wvcdm::metrics::OEMCrypto_INITIALIZED_USING_L3_L1_OPEN_FAILED);
|
||||
return result;
|
||||
}
|
||||
if (LoadLevel1(&metrics)) {
|
||||
if (LoadLevel1(metrics)) {
|
||||
LOGD("OEMCrypto_Initialize Level 1 success. I will use level 1.");
|
||||
} else {
|
||||
level1_ = FunctionPointers(); // revert to all null pointers.
|
||||
@@ -636,10 +483,7 @@ class Adapter {
|
||||
return result;
|
||||
}
|
||||
|
||||
bool LoadLevel1(wvcdm::metrics::OemCryptoDynamicAdapterMetrics* metrics) {
|
||||
if (metrics == nullptr) {
|
||||
return false;
|
||||
}
|
||||
bool LoadLevel1(wvcdm::metrics::MetricsGroup& metrics) {
|
||||
level1_valid_ = true;
|
||||
const uint32_t kMinimumVersion = 8;
|
||||
const uint32_t kMaximumVersion = 13;
|
||||
@@ -648,25 +492,37 @@ class Adapter {
|
||||
LOOKUP_ALL(8, APIVersion, OEMCrypto_APIVersion);
|
||||
LOOKUP_ALL(8, Terminate, OEMCrypto_Terminate);
|
||||
if (!level1_valid_) {
|
||||
metrics->OemCryptoDynamicAdapterMetrics::SetInitializationMode(
|
||||
M_RECORD(
|
||||
&metrics,
|
||||
oemcrypto_initialization_mode_,
|
||||
NO_TIME,
|
||||
wvcdm::metrics::OEMCrypto_INITIALIZED_USING_L3_INVALID_L1);
|
||||
return false;
|
||||
}
|
||||
OEMCryptoResult st = level1_.Initialize();
|
||||
if (st != OEMCrypto_SUCCESS) {
|
||||
LOGW("Could not initialize L1. Falling Back to L3.");
|
||||
metrics->OemCryptoDynamicAdapterMetrics::SetInitializationMode(
|
||||
M_RECORD(
|
||||
&metrics,
|
||||
oemcrypto_initialization_mode_,
|
||||
NO_TIME,
|
||||
wvcdm::metrics::OEMCrypto_INITIALIZED_USING_L3_COULD_NOT_INITIALIZE_L1);
|
||||
return false;
|
||||
}
|
||||
level1_.version = level1_.APIVersion();
|
||||
metrics->SetL1ApiVersion(level1_.version);
|
||||
metrics->SetL1MinApiVersion(kMinimumVersion);
|
||||
|
||||
M_RECORD(
|
||||
&metrics,
|
||||
oemcrypto_l1_api_version_,
|
||||
NO_TIME,
|
||||
level1_.version,
|
||||
kMinimumVersion);
|
||||
if (level1_.version < kMinimumVersion) {
|
||||
LOGW("liboemcrypto.so is version %d, not %d. Falling Back to L3.",
|
||||
level1_.version, kMinimumVersion);
|
||||
metrics->OemCryptoDynamicAdapterMetrics::SetInitializationMode(
|
||||
M_RECORD(
|
||||
&metrics,
|
||||
oemcrypto_initialization_mode_,
|
||||
NO_TIME,
|
||||
wvcdm::metrics::OEMCrypto_INITIALIZED_USING_L3_WRONG_L1_VERSION);
|
||||
level1_.Terminate();
|
||||
return false;
|
||||
@@ -741,7 +597,10 @@ class Adapter {
|
||||
|
||||
// If we have a valid keybox, initialization is done. We're good.
|
||||
if (OEMCrypto_SUCCESS == level1_.IsKeyboxValid()) {
|
||||
metrics->OemCryptoDynamicAdapterMetrics::SetInitializationMode(
|
||||
M_RECORD(
|
||||
&metrics,
|
||||
oemcrypto_initialization_mode_,
|
||||
NO_TIME,
|
||||
wvcdm::metrics::OEMCrypto_INITIALIZED_USING_L1_WITH_KEYBOX);
|
||||
return true;
|
||||
}
|
||||
@@ -750,7 +609,10 @@ class Adapter {
|
||||
// will have to be caught in the future when provisioning fails.
|
||||
if (level1_.version > 11 &&
|
||||
(level1_.GetProvisioningMethod() == OEMCrypto_OEMCertificate)) {
|
||||
metrics->OemCryptoDynamicAdapterMetrics::SetInitializationMode(
|
||||
M_RECORD(
|
||||
&metrics,
|
||||
oemcrypto_initialization_mode_,
|
||||
NO_TIME,
|
||||
wvcdm::metrics::OEMCrypto_INITIALIZED_USING_L1_WITH_PROVISIONING_3_0);
|
||||
return true;
|
||||
}
|
||||
@@ -767,10 +629,16 @@ class Adapter {
|
||||
// because we still want to test OEMCrypto in that configuration.
|
||||
LOGE("OEMCrypto uses cert as identification, but cdm does not!");
|
||||
LOGE("This will not work on a production device.");
|
||||
metrics->OemCryptoDynamicAdapterMetrics::SetInitializationMode(
|
||||
M_RECORD(
|
||||
&metrics,
|
||||
oemcrypto_initialization_mode_,
|
||||
NO_TIME,
|
||||
wvcdm::metrics::OEMCrypto_INITIALIZED_USING_L1_CERTIFICATE_MIX);
|
||||
} else {
|
||||
metrics->OemCryptoDynamicAdapterMetrics::SetInitializationMode(
|
||||
M_RECORD(
|
||||
&metrics,
|
||||
oemcrypto_initialization_mode_,
|
||||
NO_TIME,
|
||||
wvcdm::metrics::OEMCrypto_INITIALIZED_USING_L1_WITH_CERTIFICATE);
|
||||
}
|
||||
return true;
|
||||
@@ -780,7 +648,10 @@ class Adapter {
|
||||
if (!wvcdm::Properties::GetFactoryKeyboxPath(&filename)) {
|
||||
LOGW("Bad Level 1 Keybox. Falling Back to L3.");
|
||||
level1_.Terminate();
|
||||
metrics->OemCryptoDynamicAdapterMetrics::SetInitializationMode(
|
||||
M_RECORD(
|
||||
&metrics,
|
||||
oemcrypto_initialization_mode_,
|
||||
NO_TIME,
|
||||
wvcdm::metrics::OEMCrypto_INITIALIZED_USING_L3_BAD_KEYBOX);
|
||||
return false;
|
||||
}
|
||||
@@ -789,7 +660,10 @@ class Adapter {
|
||||
if (size <= 0 || !file) {
|
||||
LOGW("Could not open %s. Falling Back to L3.", filename.c_str());
|
||||
level1_.Terminate();
|
||||
metrics->OemCryptoDynamicAdapterMetrics::SetInitializationMode(
|
||||
M_RECORD(
|
||||
&metrics,
|
||||
oemcrypto_initialization_mode_,
|
||||
NO_TIME,
|
||||
wvcdm::metrics::OEMCrypto_INITIALIZED_USING_L3_COULD_NOT_OPEN_FACTORY_KEYBOX);
|
||||
return false;
|
||||
}
|
||||
@@ -800,12 +674,18 @@ class Adapter {
|
||||
LOGE("Could NOT install keybox from %s. Falling Back to L3.",
|
||||
filename.c_str());
|
||||
level1_.Terminate();
|
||||
metrics->OemCryptoDynamicAdapterMetrics::SetInitializationMode(
|
||||
M_RECORD(
|
||||
&metrics,
|
||||
oemcrypto_initialization_mode_,
|
||||
NO_TIME,
|
||||
wvcdm::metrics::OEMCrypto_INITIALIZED_USING_L3_COULD_NOT_INSTALL_KEYBOX);
|
||||
return false;
|
||||
}
|
||||
LOGI("Installed keybox from %s", filename.c_str());
|
||||
metrics->OemCryptoDynamicAdapterMetrics::SetInitializationMode(
|
||||
M_RECORD(
|
||||
&metrics,
|
||||
oemcrypto_initialization_mode_,
|
||||
NO_TIME,
|
||||
wvcdm::metrics::OEMCrypto_INITIALIZED_USING_L1_INSTALLED_KEYBOX);
|
||||
return true;
|
||||
}
|
||||
@@ -1276,26 +1156,18 @@ extern "C" OEMCryptoResult OEMCrypto_LoadKeys(
|
||||
} else {
|
||||
if (pair.fcn->LoadKeys_V9_or_V10 == NULL)
|
||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
OEMCryptoResult result = pair.fcn->LoadKeys_V9_or_V10(
|
||||
pair.session, message, message_length, signature, signature_length,
|
||||
enc_mac_key_iv, enc_mac_key, num_keys, &key_array_v10[0], pst,
|
||||
pst_length);
|
||||
// Convert a vendor specific error, to make it actionable
|
||||
if (result == kOemCryptoResultVendorSpecificError1)
|
||||
result = OEMCrypto_ERROR_USAGE_TABLE_UNRECOVERABLE;
|
||||
return result;
|
||||
return pair.fcn->LoadKeys_V9_or_V10(pair.session, message, message_length,
|
||||
signature, signature_length,
|
||||
enc_mac_key_iv, enc_mac_key, num_keys,
|
||||
&key_array_v10[0], pst, pst_length);
|
||||
}
|
||||
} else {
|
||||
if (pair.fcn->version < 13) {
|
||||
if (pair.fcn->LoadKeys_V11_or_V12 == NULL)
|
||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
OEMCryptoResult result = pair.fcn->LoadKeys_V11_or_V12(
|
||||
return pair.fcn->LoadKeys_V11_or_V12(
|
||||
pair.session, message, message_length, signature, signature_length,
|
||||
enc_mac_key_iv, enc_mac_key, num_keys, key_array, pst, pst_length);
|
||||
// Convert a vendor specific error, to make it actionable
|
||||
if (result == kOemCryptoResultVendorSpecificError1)
|
||||
result = OEMCrypto_ERROR_USAGE_TABLE_UNRECOVERABLE;
|
||||
return result;
|
||||
} else {
|
||||
if (pair.fcn->LoadKeys == NULL) return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
return pair.fcn->LoadKeys(pair.session, message, message_length,
|
||||
|
||||
@@ -14,14 +14,6 @@
|
||||
|
||||
using video_widevine::License;
|
||||
|
||||
namespace {
|
||||
|
||||
const int kCdmPolicyTimerDurationSeconds = 1;
|
||||
const int kClockSkewDelta = 5; // seconds
|
||||
const int64_t kHdcpCheckInterval = 10;
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
PolicyEngine::PolicyEngine(CdmSessionId session_id,
|
||||
@@ -36,7 +28,6 @@ PolicyEngine::PolicyEngine(CdmSessionId session_id,
|
||||
last_expiry_time_set_(false),
|
||||
was_expired_on_load_(false),
|
||||
next_renewal_time_(0),
|
||||
last_recorded_current_time_(0),
|
||||
session_id_(session_id),
|
||||
event_listener_(event_listener),
|
||||
license_keys_(new LicenseKeys),
|
||||
@@ -57,38 +48,36 @@ bool PolicyEngine::CanDecryptContent(const KeyId& key_id) {
|
||||
}
|
||||
|
||||
void PolicyEngine::InitDevice(CryptoSession* crypto_session) {
|
||||
current_resolution_ = kNoResolution;
|
||||
current_resolution_ = HDCP_UNSPECIFIED_VIDEO_RESOLUTION;
|
||||
next_device_check_ = 0;
|
||||
crypto_session_ = crypto_session;
|
||||
}
|
||||
|
||||
void PolicyEngine::CheckDevice(int64_t current_time) {
|
||||
if (current_time < next_device_check_) {
|
||||
return;
|
||||
}
|
||||
void PolicyEngine::SetDeviceResolution(uint32_t width, uint32_t height) {
|
||||
current_resolution_ = width * height;
|
||||
CheckDeviceHdcpStatus();
|
||||
}
|
||||
|
||||
if (!license_keys_->Empty() && current_resolution_ != kNoResolution) {
|
||||
void PolicyEngine::CheckDeviceHdcpStatusOnTimer(int64_t current_time) {
|
||||
if (current_time >= next_device_check_) {
|
||||
CheckDeviceHdcpStatus();
|
||||
next_device_check_ = current_time + HDCP_DEVICE_CHECK_INTERVAL;
|
||||
}
|
||||
}
|
||||
|
||||
void PolicyEngine::CheckDeviceHdcpStatus() {
|
||||
if (!license_keys_->Empty()) {
|
||||
CryptoSession::HdcpCapability current_hdcp_level;
|
||||
CryptoSession::HdcpCapability ignored;
|
||||
if (!crypto_session_->GetHdcpCapabilities(¤t_hdcp_level, &ignored)) {
|
||||
current_hdcp_level = HDCP_NONE;
|
||||
}
|
||||
|
||||
bool new_usable_keys = false;
|
||||
bool keys_changed =
|
||||
license_keys_->ApplyStatusChange(NULL, // new_status
|
||||
¤t_resolution_,
|
||||
¤t_hdcp_level,
|
||||
&new_usable_keys);
|
||||
NotifyIfKeysChanged(keys_changed, new_usable_keys);
|
||||
|
||||
next_device_check_ = current_time + kHdcpCheckInterval;
|
||||
license_keys_->ApplyConstraints(current_resolution_, current_hdcp_level);
|
||||
}
|
||||
}
|
||||
|
||||
void PolicyEngine::OnTimerEvent() {
|
||||
last_recorded_current_time_ += kCdmPolicyTimerDurationSeconds;
|
||||
int64_t current_time = GetCurrentTime();
|
||||
int64_t current_time = clock_->GetCurrentTime();
|
||||
|
||||
// If we have passed the grace period, the expiration will update.
|
||||
if (grace_period_end_time_ == 0 && HasPlaybackStarted(current_time)) {
|
||||
@@ -100,19 +89,21 @@ void PolicyEngine::OnTimerEvent() {
|
||||
if (HasLicenseOrPlaybackDurationExpired(current_time) &&
|
||||
license_state_ != kLicenseStateExpired) {
|
||||
license_state_ = kLicenseStateExpired;
|
||||
UpdateKeyStatus(kKeyStatusExpired);
|
||||
NotifyKeysChange(kKeyStatusExpired);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check device conditions that affect playability (HDCP, resolution)
|
||||
CheckDevice(current_time);
|
||||
CheckDeviceHdcpStatusOnTimer(current_time);
|
||||
|
||||
// Test to determine if renewal should be attempted.
|
||||
bool renewal_needed = false;
|
||||
|
||||
// Test to determine if renewal should be attempted.
|
||||
switch (license_state_) {
|
||||
case kLicenseStateCanPlay: {
|
||||
if (HasRenewalDelayExpired(current_time)) renewal_needed = true;
|
||||
// HDCP may change, so force a check.
|
||||
NotifyKeysChange(kKeyStatusUsable);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -129,7 +120,7 @@ void PolicyEngine::OnTimerEvent() {
|
||||
case kLicenseStatePending: {
|
||||
if (current_time >= license_start_time_) {
|
||||
license_state_ = kLicenseStateCanPlay;
|
||||
UpdateKeyStatus(kKeyStatusUsable);
|
||||
NotifyKeysChange(kKeyStatusUsable);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -141,7 +132,7 @@ void PolicyEngine::OnTimerEvent() {
|
||||
|
||||
default: {
|
||||
license_state_ = kLicenseStateExpired;
|
||||
UpdateKeyStatus(kKeyStatusInternalError);
|
||||
NotifyKeysChange(kKeyStatusInternalError);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -166,7 +157,7 @@ void PolicyEngine::SetLicenseForRelease(const License& license) {
|
||||
policy_.Clear();
|
||||
|
||||
// Expire any old keys.
|
||||
UpdateKeyStatus(kKeyStatusExpired);
|
||||
NotifyKeysChange(kKeyStatusExpired);
|
||||
UpdateLicense(license);
|
||||
}
|
||||
|
||||
@@ -195,21 +186,21 @@ void PolicyEngine::UpdateLicense(const License& license) {
|
||||
license_start_time_ = license.license_start_time();
|
||||
next_renewal_time_ = license_start_time_ + policy_.renewal_delay_seconds();
|
||||
|
||||
int64_t current_time = GetCurrentTime();
|
||||
int64_t current_time = clock_->GetCurrentTime();
|
||||
if (!policy_.can_play() ||
|
||||
HasLicenseOrPlaybackDurationExpired(current_time)) {
|
||||
license_state_ = kLicenseStateExpired;
|
||||
UpdateKeyStatus(kKeyStatusExpired);
|
||||
NotifyKeysChange(kKeyStatusExpired);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update state
|
||||
if (current_time >= license_start_time_) {
|
||||
license_state_ = kLicenseStateCanPlay;
|
||||
UpdateKeyStatus(kKeyStatusUsable);
|
||||
NotifyKeysChange(kKeyStatusUsable);
|
||||
} else {
|
||||
license_state_ = kLicenseStatePending;
|
||||
UpdateKeyStatus(kKeyStatusPending);
|
||||
NotifyKeysChange(kKeyStatusPending);
|
||||
}
|
||||
NotifyExpirationUpdate(current_time);
|
||||
}
|
||||
@@ -220,7 +211,7 @@ void PolicyEngine::BeginDecryption() {
|
||||
case kLicenseStateCanPlay:
|
||||
case kLicenseStateNeedRenewal:
|
||||
case kLicenseStateWaitingLicenseUpdate:
|
||||
playback_start_time_ = GetCurrentTime();
|
||||
playback_start_time_ = clock_->GetCurrentTime();
|
||||
last_playback_time_ = playback_start_time_;
|
||||
if (policy_.play_start_grace_period_seconds() == 0)
|
||||
grace_period_end_time_ = playback_start_time_;
|
||||
@@ -240,7 +231,7 @@ void PolicyEngine::BeginDecryption() {
|
||||
}
|
||||
|
||||
void PolicyEngine::DecryptionEvent() {
|
||||
last_playback_time_ = GetCurrentTime();
|
||||
last_playback_time_ = clock_->GetCurrentTime();
|
||||
}
|
||||
|
||||
void PolicyEngine::NotifyResolution(uint32_t width, uint32_t height) {
|
||||
@@ -249,12 +240,12 @@ void PolicyEngine::NotifyResolution(uint32_t width, uint32_t height) {
|
||||
|
||||
void PolicyEngine::NotifySessionExpiration() {
|
||||
license_state_ = kLicenseStateExpired;
|
||||
UpdateKeyStatus(kKeyStatusExpired);
|
||||
NotifyKeysChange(kKeyStatusExpired);
|
||||
}
|
||||
|
||||
CdmResponseType PolicyEngine::Query(CdmQueryMap* query_response) {
|
||||
std::stringstream ss;
|
||||
int64_t current_time = GetCurrentTime();
|
||||
int64_t current_time = clock_->GetCurrentTime();
|
||||
|
||||
if (license_state_ == kLicenseStateInitial) {
|
||||
query_response->clear();
|
||||
@@ -295,7 +286,7 @@ CdmResponseType PolicyEngine::QueryKeyAllowedUsage(
|
||||
bool PolicyEngine::GetSecondsSinceStarted(int64_t* seconds_since_started) {
|
||||
if (playback_start_time_ == 0) return false;
|
||||
|
||||
*seconds_since_started = GetCurrentTime() - playback_start_time_;
|
||||
*seconds_since_started = clock_->GetCurrentTime() - playback_start_time_;
|
||||
return (*seconds_since_started >= 0) ? true : false;
|
||||
}
|
||||
|
||||
@@ -303,12 +294,12 @@ bool PolicyEngine::GetSecondsSinceLastPlayed(
|
||||
int64_t* seconds_since_last_played) {
|
||||
if (last_playback_time_ == 0) return false;
|
||||
|
||||
*seconds_since_last_played = GetCurrentTime() - last_playback_time_;
|
||||
*seconds_since_last_played = clock_->GetCurrentTime() - last_playback_time_;
|
||||
return (*seconds_since_last_played >= 0) ? true : false;
|
||||
}
|
||||
|
||||
int64_t PolicyEngine::GetLicenseOrPlaybackDurationRemaining() {
|
||||
const int64_t current_time = GetCurrentTime();
|
||||
const int64_t current_time = clock_->GetCurrentTime();
|
||||
const int64_t expiry_time =
|
||||
GetExpiryTime(current_time,
|
||||
/* ignore_soft_enforce_playback_duration */ false);
|
||||
@@ -332,7 +323,7 @@ void PolicyEngine::RestorePlaybackTimes(int64_t playback_start_time,
|
||||
playback_start_time_ = grace_period_end_time;
|
||||
}
|
||||
|
||||
const int64_t current_time = GetCurrentTime();
|
||||
const int64_t current_time = clock_->GetCurrentTime();
|
||||
const int64_t expiry_time =
|
||||
GetExpiryTime(current_time,
|
||||
/* ignore_soft_enforce_playback_duration */ true);
|
||||
@@ -403,8 +394,6 @@ int64_t PolicyEngine::GetLicenseOrRentalDurationRemaining(
|
||||
if (license_expiry_time == NEVER_EXPIRES) return LLONG_MAX;
|
||||
if (license_expiry_time < current_time) return 0;
|
||||
const int64_t policy_license_duration = policy_.license_duration_seconds();
|
||||
if (policy_license_duration == NEVER_EXPIRES)
|
||||
return license_expiry_time - current_time;
|
||||
return std::min(license_expiry_time - current_time, policy_license_duration);
|
||||
}
|
||||
|
||||
@@ -440,23 +429,19 @@ bool PolicyEngine::HasRenewalRetryIntervalExpired(int64_t current_time) {
|
||||
next_renewal_time_ <= current_time;
|
||||
}
|
||||
|
||||
void PolicyEngine::UpdateKeyStatus(CdmKeyStatus new_status) {
|
||||
bool new_usable_keys = false;
|
||||
bool keys_changed =
|
||||
license_keys_->ApplyStatusChange(&new_status,
|
||||
NULL, // new_resolution
|
||||
NULL, // new_hdcp_level
|
||||
&new_usable_keys);
|
||||
NotifyIfKeysChanged(keys_changed, new_usable_keys);
|
||||
}
|
||||
|
||||
void PolicyEngine::NotifyIfKeysChanged(bool keys_changed,
|
||||
bool new_usable_keys) {
|
||||
void PolicyEngine::NotifyKeysChange(CdmKeyStatus new_status) {
|
||||
bool keys_changed;
|
||||
bool has_new_usable_key = false;
|
||||
if (new_status == kKeyStatusUsable) {
|
||||
CheckDeviceHdcpStatus();
|
||||
}
|
||||
keys_changed = license_keys_->ApplyStatusChange(new_status,
|
||||
&has_new_usable_key);
|
||||
if (event_listener_ && keys_changed) {
|
||||
CdmKeyStatusMap content_keys;
|
||||
license_keys_->ExtractKeyStatuses(&content_keys);
|
||||
event_listener_->OnSessionKeysChange(session_id_, content_keys,
|
||||
new_usable_keys);
|
||||
has_new_usable_key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -472,15 +457,6 @@ void PolicyEngine::NotifyExpirationUpdate(int64_t current_time) {
|
||||
last_expiry_time_set_ = true;
|
||||
}
|
||||
|
||||
int64_t PolicyEngine::GetCurrentTime() {
|
||||
int64_t current_time = clock_->GetCurrentTime();
|
||||
if (current_time + kClockSkewDelta < last_recorded_current_time_)
|
||||
current_time = last_recorded_current_time_;
|
||||
else
|
||||
last_recorded_current_time_ = current_time;
|
||||
return current_time;
|
||||
}
|
||||
|
||||
void PolicyEngine::set_clock(Clock* clock) { clock_.reset(clock); }
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
@@ -29,15 +29,14 @@ RSA* GetKey(const std::string& serialized_key) {
|
||||
return NULL;
|
||||
}
|
||||
RSA* key = d2i_RSAPublicKey_bio(bio, NULL);
|
||||
BIO_free(bio);
|
||||
|
||||
if (key == NULL) {
|
||||
LOGE("GetKey: RSA key deserialization failure: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
BIO_free(bio);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
BIO_free(bio);
|
||||
return key;
|
||||
}
|
||||
|
||||
@@ -183,7 +182,7 @@ bool RsaPublicKey::Encrypt(const std::string& clear_message,
|
||||
|
||||
// LogOpenSSLError is a callback from OpenSSL which is called with each error
|
||||
// in the thread's error queue.
|
||||
static int LogOpenSSLError(const char *msg, size_t /* len */, void */* ctx */) {
|
||||
static int LogOpenSSLError(const char* msg, size_t /* len */, void* /* ctx */) {
|
||||
LOGE(" %s", msg);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -67,6 +67,40 @@ CdmResponseType ServiceCertificate::Init(const std::string& raw_certificate) {
|
||||
return VerifyAndExtract(raw_certificate);
|
||||
}
|
||||
|
||||
CdmResponseType ServiceCertificate::VerifySignedMessage(
|
||||
const std::string& message, const std::string& signature) {
|
||||
|
||||
if (certificate_.empty()) {
|
||||
LOGE("ServiceCertificate::VerifySignedMessage: "
|
||||
"service certificate is not properly initialized");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
DrmDeviceCertificate service_certificate;
|
||||
if (!service_certificate.ParseFromString(certificate_)) {
|
||||
LOGE("ServiceCertificate::EncryptClientId: unable to parse retrieved "
|
||||
"service certificate");
|
||||
return PARSE_SERVICE_CERTIFICATE_ERROR;
|
||||
}
|
||||
|
||||
if (service_certificate.type() !=
|
||||
video_widevine::DrmDeviceCertificate_CertificateType_SERVICE) {
|
||||
LOGE("ServiceCertificate::EncryptClientId: retrieved certificate not of "
|
||||
"type service, %d", service_certificate.type());
|
||||
return SERVICE_CERTIFICATE_TYPE_ERROR;
|
||||
}
|
||||
|
||||
// TODO(gm) verify; rework error codes.
|
||||
RsaPublicKey rsa;
|
||||
if (!rsa.Init(service_certificate.public_key()))
|
||||
return CLIENT_ID_RSA_INIT_ERROR;
|
||||
|
||||
if (!rsa.VerifySignature(message, signature))
|
||||
return CLIENT_ID_RSA_ENCRYPT_ERROR;
|
||||
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
CdmResponseType ServiceCertificate::EncryptClientId(
|
||||
CryptoSession* crypto_session, const ClientIdentification* clear_client_id,
|
||||
EncryptedClientIdentification* encrypted_client_id) {
|
||||
|
||||
@@ -10,14 +10,28 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#include <modp_b64.h>
|
||||
#include <modp_b64w.h>
|
||||
|
||||
#include "log.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
static bool CharToDigit(char ch, unsigned char* digit) {
|
||||
static const char kBase64Codes[] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
||||
|
||||
// Gets the low |n| bits of |in|.
|
||||
#define GET_LOW_BITS(in, n) ((in) & ((1 << (n)) - 1))
|
||||
// Gets the given (zero-indexed) bits [a, b) of |in|.
|
||||
#define GET_BITS(in, a, b) GET_LOW_BITS((in) >> (a), (b) - (a))
|
||||
// Calculates a/b using round-up division (only works for positive numbers).
|
||||
#define CEIL_DIVIDE(a, b) ((((a) - 1) / (b)) + 1)
|
||||
|
||||
int DecodeBase64Char(char c) {
|
||||
const char* it = strchr(kBase64Codes, c);
|
||||
if (it == NULL)
|
||||
return -1;
|
||||
return it - kBase64Codes;
|
||||
}
|
||||
|
||||
bool DecodeHexChar(char ch, unsigned char* digit) {
|
||||
if (ch >= '0' && ch <= '9') {
|
||||
*digit = ch - '0';
|
||||
} else {
|
||||
@@ -43,8 +57,8 @@ std::vector<uint8_t> a2b_hex(const std::string& byte) {
|
||||
for (unsigned int i = 0; i < count / 2; ++i) {
|
||||
unsigned char msb = 0; // most significant 4 bits
|
||||
unsigned char lsb = 0; // least significant 4 bits
|
||||
if (!CharToDigit(byte[i * 2], &msb) ||
|
||||
!CharToDigit(byte[i * 2 + 1], &lsb)) {
|
||||
if (!DecodeHexChar(byte[i * 2], &msb) ||
|
||||
!DecodeHexChar(byte[i * 2 + 1], &lsb)) {
|
||||
LOGE("Invalid hex value %c%c at index %d", byte[i * 2], byte[i * 2 + 1],
|
||||
i);
|
||||
return array;
|
||||
@@ -80,23 +94,50 @@ std::string b2a_hex(const std::string& byte) {
|
||||
}
|
||||
|
||||
// Encode for standard base64 encoding (RFC4648).
|
||||
// https://en.wikipedia.org/wiki/Base64
|
||||
// Text | M | a | n |
|
||||
// ASCI | 77 (0x4d) | 97 (0x61) | 110 (0x6e) |
|
||||
// Bits | 0 1 0 0 1 1 0 1 0 1 1 0 0 0 0 1 0 1 1 0 1 1 1 0 |
|
||||
// Index | 19 | 22 | 5 | 46 |
|
||||
// Base64 | T | W | F | u |
|
||||
// | <----------------- 24-bits -----------------> |
|
||||
std::string Base64Encode(const std::vector<uint8_t>& bin_input) {
|
||||
if (bin_input.empty()) {
|
||||
return std::string();
|
||||
}
|
||||
|
||||
int in_size = bin_input.size();
|
||||
std::string b64_output(modp_b64_encode_len(in_size), 0);
|
||||
// |temp| stores a 24-bit block that is treated as an array where insertions
|
||||
// occur from high to low.
|
||||
uint32_t temp = 0;
|
||||
size_t out_index = 0;
|
||||
const size_t out_size = CEIL_DIVIDE(bin_input.size(), 3) * 4;
|
||||
std::string result(out_size, '\0');
|
||||
for (size_t i = 0; i < bin_input.size(); i++) {
|
||||
// "insert" 8-bits of data
|
||||
temp |= (bin_input[i] << ((2 - (i % 3)) * 8));
|
||||
|
||||
int out_size = modp_b64_encode(
|
||||
&b64_output[0], reinterpret_cast<const char*>(&bin_input[0]), in_size);
|
||||
if (out_size == -1) {
|
||||
LOGE("Base64Encode failed");
|
||||
return std::string();
|
||||
if (i % 3 == 2) {
|
||||
result[out_index++] = kBase64Codes[GET_BITS(temp, 18, 24)];
|
||||
result[out_index++] = kBase64Codes[GET_BITS(temp, 12, 18)];
|
||||
result[out_index++] = kBase64Codes[GET_BITS(temp, 6, 12)];
|
||||
result[out_index++] = kBase64Codes[GET_BITS(temp, 0, 6)];
|
||||
temp = 0;
|
||||
}
|
||||
}
|
||||
|
||||
b64_output.resize(out_size);
|
||||
return b64_output;
|
||||
if (bin_input.size() % 3 == 1) {
|
||||
result[out_index++] = kBase64Codes[GET_BITS(temp, 18, 24)];
|
||||
result[out_index++] = kBase64Codes[GET_BITS(temp, 12, 18)];
|
||||
result[out_index++] = '=';
|
||||
result[out_index++] = '=';
|
||||
} else if (bin_input.size() % 3 == 2) {
|
||||
result[out_index++] = kBase64Codes[GET_BITS(temp, 18, 24)];
|
||||
result[out_index++] = kBase64Codes[GET_BITS(temp, 12, 18)];
|
||||
result[out_index++] = kBase64Codes[GET_BITS(temp, 6, 12)];
|
||||
result[out_index++] = '=';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Filename-friendly base64 encoding (RFC4648), commonly referred to
|
||||
@@ -111,18 +152,14 @@ std::string Base64SafeEncode(const std::vector<uint8_t>& bin_input) {
|
||||
return std::string();
|
||||
}
|
||||
|
||||
int in_size = bin_input.size();
|
||||
std::string b64_output(modp_b64w_encode_len(in_size), 0);
|
||||
|
||||
int out_size = modp_b64w_encode(
|
||||
&b64_output[0], reinterpret_cast<const char*>(&bin_input[0]), in_size);
|
||||
if (out_size == -1) {
|
||||
LOGE("Base64SafeEncode failed");
|
||||
return std::string();
|
||||
std::string ret = Base64Encode(bin_input);
|
||||
for (size_t i = 0; i < ret.size(); i++) {
|
||||
if (ret[i] == '+')
|
||||
ret[i] = '-';
|
||||
else if (ret[i] == '/')
|
||||
ret[i] = '_';
|
||||
}
|
||||
|
||||
b64_output.resize(out_size);
|
||||
return b64_output;
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string Base64SafeEncodeNoPad(const std::vector<uint8_t>& bin_input) {
|
||||
@@ -138,17 +175,57 @@ std::vector<uint8_t> Base64Decode(const std::string& b64_input) {
|
||||
return std::vector<uint8_t>();
|
||||
}
|
||||
|
||||
int in_size = b64_input.size();
|
||||
std::vector<uint8_t> bin_output(modp_b64_decode_len(in_size), 0);
|
||||
int out_size = modp_b64_decode(reinterpret_cast<char*>(&bin_output[0]),
|
||||
b64_input.data(), in_size);
|
||||
if (out_size == -1) {
|
||||
LOGE("Base64Decode failed");
|
||||
return std::vector<uint8_t>(0);
|
||||
const size_t out_size_max = CEIL_DIVIDE(b64_input.size() * 3, 4);
|
||||
std::vector<uint8_t> result(out_size_max, '\0');
|
||||
|
||||
// |temp| stores 24-bits of data that is treated as an array where insertions
|
||||
// occur from high to low.
|
||||
uint32_t temp = 0;
|
||||
size_t out_index = 0;
|
||||
size_t i;
|
||||
for (i = 0; i < b64_input.size(); i++) {
|
||||
if (b64_input[i] == '=') {
|
||||
// Verify an '=' only appears at the end. We want i to remain at the
|
||||
// first '=', so we need an inner loop.
|
||||
for (size_t j = i; j < b64_input.size(); j++) {
|
||||
if (b64_input[j] != '=') {
|
||||
LOGE("base64Decode failed");
|
||||
return std::vector<uint8_t>();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
const int decoded = DecodeBase64Char(b64_input[i]);
|
||||
if (decoded < 0) {
|
||||
LOGE("base64Decode failed");
|
||||
return std::vector<uint8_t>();
|
||||
}
|
||||
// "insert" 6-bits of data
|
||||
temp |= (decoded << ((3 - (i % 4)) * 6));
|
||||
|
||||
if (i % 4 == 3) {
|
||||
result[out_index++] = GET_BITS(temp, 16, 24);
|
||||
result[out_index++] = GET_BITS(temp, 8, 16);
|
||||
result[out_index++] = GET_BITS(temp, 0, 8);
|
||||
temp = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bin_output.resize(out_size);
|
||||
return bin_output;
|
||||
switch (i % 4) {
|
||||
case 1:
|
||||
LOGE("base64Decode failed");
|
||||
return std::vector<uint8_t>();
|
||||
case 2:
|
||||
result[out_index++] = GET_BITS(temp, 16, 24);
|
||||
break;
|
||||
case 3:
|
||||
result[out_index++] = GET_BITS(temp, 16, 24);
|
||||
result[out_index++] = GET_BITS(temp, 8, 16);
|
||||
break;
|
||||
}
|
||||
result.resize(out_index);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Decode for Filename-friendly base64 encoding (RFC4648), commonly referred
|
||||
@@ -158,17 +235,16 @@ std::vector<uint8_t> Base64SafeDecode(const std::string& b64_input) {
|
||||
return std::vector<uint8_t>();
|
||||
}
|
||||
|
||||
int in_size = b64_input.size();
|
||||
std::vector<uint8_t> bin_output(modp_b64w_decode_len(in_size), 0);
|
||||
int out_size = modp_b64w_decode(reinterpret_cast<char*>(&bin_output[0]),
|
||||
b64_input.data(), in_size);
|
||||
if (out_size == -1) {
|
||||
LOGE("Base64SafeDecode failed");
|
||||
return std::vector<uint8_t>(0);
|
||||
// Make a copy so we can modify it to replace the web-safe special characters
|
||||
// with the normal ones.
|
||||
std::string input_copy = b64_input;
|
||||
for (size_t i = 0; i < input_copy.size(); i++) {
|
||||
if (input_copy[i] == '-')
|
||||
input_copy[i] = '+';
|
||||
else if (input_copy[i] == '_')
|
||||
input_copy[i] = '/';
|
||||
}
|
||||
|
||||
bin_output.resize(out_size);
|
||||
return bin_output;
|
||||
return Base64Decode(input_copy);
|
||||
}
|
||||
|
||||
std::string HexEncode(const uint8_t* in_buffer, unsigned int size) {
|
||||
|
||||
@@ -24,7 +24,6 @@ UsageTableHeader::UsageTableHeader()
|
||||
: security_level_(kSecurityLevelUninitialized),
|
||||
requested_security_level_(kLevelDefault),
|
||||
is_inited_(false) {
|
||||
file_system_.reset(new FileSystem());
|
||||
file_handle_.reset(new DeviceFiles(file_system_.get()));
|
||||
}
|
||||
|
||||
@@ -110,7 +109,7 @@ CdmResponseType UsageTableHeader::AddEntry(
|
||||
size_t number_of_entries = usage_entry_info_.size();
|
||||
usage_entry_info_.resize(*usage_entry_number + 1);
|
||||
for (size_t i = number_of_entries; i < usage_entry_info_.size() - 1; ++i) {
|
||||
usage_entry_info_[i].storage_type = kStorageUnknown;
|
||||
usage_entry_info_[i].storage_type = kStorageTypeUnknown;
|
||||
usage_entry_info_[i].key_set_id.clear();
|
||||
usage_entry_info_[i].usage_info_file_name.clear();
|
||||
}
|
||||
@@ -183,7 +182,7 @@ CdmResponseType UsageTableHeader::DeleteEntry(uint32_t usage_entry_number,
|
||||
if (status == NO_ERROR) swap_usage_entry_valid = true;
|
||||
break;
|
||||
}
|
||||
case kStorageUnknown:
|
||||
case kStorageTypeUnknown:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -199,7 +198,7 @@ CdmResponseType UsageTableHeader::DeleteEntry(uint32_t usage_entry_number,
|
||||
// If unable to move entry, unset storage type of entry to be deleted and
|
||||
// resize |usage_entry_info_| so that swap usage entry is the last entry.
|
||||
if (status != NO_ERROR) {
|
||||
usage_entry_info_[usage_entry_number].storage_type = kStorageUnknown;
|
||||
usage_entry_info_[usage_entry_number].storage_type = kStorageTypeUnknown;
|
||||
usage_entry_info_[usage_entry_number].key_set_id.clear();
|
||||
if (usage_entry_info_.size() - 1 == swap_entry_number) {
|
||||
file_handle_->StoreUsageTableInfo(usage_table_header_,
|
||||
@@ -286,7 +285,7 @@ CdmResponseType UsageTableHeader::GetEntry(uint32_t usage_entry_number,
|
||||
&last_playback_time, &grace_period_end_time, &app_parameters,
|
||||
usage_entry, &entry_number)) {
|
||||
LOGE("UsageTableHeader::GetEntry: Failed to retrieve license");
|
||||
return USAGE_RETRIEVE_LICENSE_FAILED;
|
||||
return USAGE_GET_ENTRY_RETRIEVE_LICENSE_FAILED;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -302,17 +301,17 @@ CdmResponseType UsageTableHeader::GetEntry(uint32_t usage_entry_number,
|
||||
usage_entry, &entry_number)) {
|
||||
LOGE(
|
||||
"UsageTableHeader::GetEntry: Failed to retrieve usage information");
|
||||
return USAGE_RETRIEVE_USAGE_INFO_FAILED;
|
||||
return USAGE_GET_ENTRY_RETRIEVE_USAGE_INFO_FAILED;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kStorageUnknown:
|
||||
case kStorageTypeUnknown:
|
||||
default:
|
||||
LOGE(
|
||||
"UsageTableHeader::GetEntry: Attempting to retrieve usage "
|
||||
"information from unknown storage type: %d",
|
||||
usage_entry_info_[usage_entry_number].storage_type);
|
||||
return USAGE_RETRIEVE_INVALID_STORAGE_TYPE;
|
||||
return USAGE_GET_ENTRY_RETRIEVE_INVALID_STORAGE_TYPE;
|
||||
}
|
||||
|
||||
if (usage_entry_number != entry_number) {
|
||||
@@ -343,7 +342,7 @@ CdmResponseType UsageTableHeader::StoreEntry(uint32_t usage_entry_number,
|
||||
&last_playback_time, &grace_period_end_time, &app_parameters,
|
||||
&entry, &entry_number)) {
|
||||
LOGE("UsageTableHeader::StoreEntry: Failed to retrieve license");
|
||||
return USAGE_RETRIEVE_LICENSE_FAILED;
|
||||
return USAGE_STORE_ENTRY_RETRIEVE_LICENSE_FAILED;
|
||||
}
|
||||
if (!handle->StoreLicense(
|
||||
usage_entry_info_[usage_entry_number].key_set_id, license_state,
|
||||
@@ -368,7 +367,7 @@ CdmResponseType UsageTableHeader::StoreEntry(uint32_t usage_entry_number,
|
||||
LOGE(
|
||||
"UsageTableHeader::StoreEntry: Failed to retrieve usage "
|
||||
"information");
|
||||
return USAGE_RETRIEVE_USAGE_INFO_FAILED;
|
||||
return USAGE_STORE_ENTRY_RETRIEVE_USAGE_INFO_FAILED;
|
||||
}
|
||||
handle->DeleteUsageInfo(
|
||||
usage_entry_info_[usage_entry_number].usage_info_file_name,
|
||||
@@ -383,13 +382,13 @@ CdmResponseType UsageTableHeader::StoreEntry(uint32_t usage_entry_number,
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kStorageUnknown:
|
||||
case kStorageTypeUnknown:
|
||||
default:
|
||||
LOGE(
|
||||
"UsageTableHeader::GetUsageEntry: Attempting to retrieve usage "
|
||||
"information from unknown storage type: %d",
|
||||
usage_entry_info_[usage_entry_number].storage_type);
|
||||
return USAGE_RETRIEVE_INVALID_STORAGE_TYPE;
|
||||
return USAGE_STORE_ENTRY_RETRIEVE_INVALID_STORAGE_TYPE;
|
||||
}
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user