Files
android/libwvdrmengine/cdm/core/src/cdm_session.cpp
John W. Bruce 5d4932264f Allow Reloading Partially-Released Licenses
(This is a merge of http://go/wvgerrit/62780)

The EME spec requires that it be possible to do the following flow:

  1) Generate a release request for a persistent session.
  2) Close the session.
  3) Load the persistent session.
  4) Update the session with the release response.

This flow is used by Netflix in their API as well. However, our code did
not support this flow, as it rejected attempts to reload
partially-released sessions.

This patch changes attempts to load sessions that have already had
release messages generated into release-retry reloads, allowing them to
be released.

Bug: 113167010
Test: CE CDM Unit Tests
Test: Android CDM Unit Tests
Change-Id: I75bb7c75911e0fad1584bd8dd27f83c17f73bf45
2018-10-09 14:08:28 -07:00

1122 lines
37 KiB
C++

// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#include "cdm_session.h"
#include <assert.h>
#include <stdlib.h>
#include <iostream>
#include <sstream>
#include "cdm_engine.h"
#include "clock.h"
#include "file_store.h"
#include "log.h"
#include "properties.h"
#include "string_conversions.h"
#include "usage_table_header.h"
#include "wv_cdm_constants.h"
#include "wv_cdm_event_listener.h"
namespace {
const size_t kKeySetIdLength = 14;
} // namespace
namespace wvcdm {
CdmSession::CdmSession(FileSystem* file_system,
metrics::SessionMetrics* metrics)
: metrics_(metrics),
initialized_(false),
closed_(true),
file_handle_(new DeviceFiles(file_system)),
license_received_(false),
is_offline_(false),
is_release_(false),
is_temporary_(false),
security_level_(kSecurityLevelUninitialized),
requested_security_level_(kLevelDefault),
is_initial_decryption_(true),
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(CryptoSession::MakeCryptoSession(crypto_metrics_));
life_span_.Start();
}
CdmSession::~CdmSession() {
if (usage_support_type_ == kUsageEntrySupport &&
has_provider_session_token() &&
usage_table_header_ != NULL) {
UpdateUsageEntryInformation();
}
if (!key_set_id_.empty()) {
// Unreserve the license ID.
file_handle_->UnreserveLicenseId(key_set_id_);
}
Properties::RemoveSessionPropertySet(session_id_);
if (metrics_) {
M_RECORD(metrics_, cdm_session_life_span_, life_span_.AsMs());
metrics_->SetCompleted();
}
}
CdmResponseType CdmSession::Init(
CdmClientPropertySet* cdm_client_property_set) {
return Init(cdm_client_property_set, NULL, NULL);
}
CdmResponseType CdmSession::Init(CdmClientPropertySet* cdm_client_property_set,
const CdmSessionId* forced_session_id,
WvCdmEventListener* event_listener) {
if (initialized_) {
LOGE("CdmSession::Init: Failed due to previous initialization");
return REINIT_ERROR;
}
if (cdm_client_property_set && cdm_client_property_set->security_level() ==
QUERY_VALUE_SECURITY_LEVEL_L3) {
requested_security_level_ = kLevel3;
security_level_ = kSecurityLevelL3;
}
CdmResponseType sts;
M_TIME(sts = crypto_session_->Open(requested_security_level_),
crypto_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_);
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.
// Otherwise, try to fetch device certificate. If not successful and
// provisioning is supported, return NEED_PROVISIONING. Otherwise, return
// an error.
// client_token and client_token_type are determined here; they are needed
// to initialize the license parser.
std::string client_token;
std::string serial_number;
CdmClientTokenType client_token_type =
crypto_session_->GetPreProvisionTokenType();
// License server client ID token is a stored certificate. Stage it or
// indicate that provisioning is needed. Get token from stored certificate
std::string wrapped_key;
if (!file_handle_->RetrieveCertificate(&client_token, &wrapped_key,
&serial_number, NULL)) {
return NEED_PROVISIONING;
}
bool load_cert_sts;
M_TIME(
load_cert_sts = crypto_session_->LoadCertificatePrivateKey(wrapped_key),
crypto_metrics_, crypto_session_load_certificate_private_key_,
load_cert_sts);
if (!load_cert_sts) {
return NEED_PROVISIONING;
}
client_token_type = kClientTokenDrmCert;
// Session is provisioned with certificate needed to construct
// license request (or with keybox).
if (forced_session_id) {
key_set_id_ = *forced_session_id;
} else {
bool ok = GenerateKeySetId(&key_set_id_);
(void)ok; // ok is now used when assertions are turned off.
assert(ok);
}
session_id_ =
Properties::AlwaysUseKeySetIds() ? key_set_id_ : GenerateSessionId();
metrics_->SetSessionId(session_id_);
if (session_id_.empty()) {
LOGE("CdmSession::Init: empty session ID");
return EMPTY_SESSION_ID;
}
if (cdm_client_property_set)
Properties::AddSessionPropertySet(session_id_, cdm_client_property_set);
if (!mock_license_parser_in_use_)
license_parser_.reset(new CdmLicense(session_id_));
if (!mock_policy_engine_in_use_)
policy_engine_.reset(
new PolicyEngine(session_id_, event_listener, crypto_session_.get()));
std::string service_certificate;
if (!Properties::GetServiceCertificate(session_id_, &service_certificate))
service_certificate.clear();
if (!license_parser_->Init(client_token, client_token_type, serial_number,
Properties::UsePrivacyMode(session_id_),
service_certificate, crypto_session_.get(),
policy_engine_.get()))
return LICENSE_PARSER_INIT_ERROR;
license_received_ = false;
is_initial_decryption_ = true;
initialized_ = true;
closed_ = false;
return NO_ERROR;
}
CdmResponseType CdmSession::RestoreOfflineSession(
const CdmKeySetId& key_set_id, CdmLicenseType license_type) {
if (!initialized_) {
LOGE("CdmSession::RestoreOfflineSession: not initialized");
return NOT_INITIALIZED_ERROR;
}
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;
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());
return GET_LICENSE_ERROR;
}
// Attempts to restore a released offline license are treated as a release
// retry.
if (license_state == DeviceFiles::kLicenseStateReleasing) {
license_type = kLicenseTypeRelease;
}
// Only restore offline licenses if they are active or this is 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);
return GET_RELEASED_LICENSE_ERROR;
}
std::string provider_session_token;
if (usage_support_type_ == kUsageEntrySupport) {
if (!license_parser_->ExtractProviderSessionToken(
key_response_, &provider_session_token) ||
usage_table_header_ == NULL) {
provider_session_token.clear();
} else {
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_)) {
return RELEASE_LICENSE_ERROR_1;
}
} else {
if (!license_parser_->RestoreOfflineLicense(
key_request_, key_response_, offline_key_renewal_response_,
playback_start_time, last_playback_time, grace_period_end_time,
this)) {
return RESTORE_OFFLINE_LICENSE_ERROR_2;
}
}
if (usage_support_type_ == kUsageEntrySupport &&
!provider_session_token.empty() && usage_table_header_ != NULL) {
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;
return KEY_ADDED;
}
CdmResponseType CdmSession::RestoreUsageSession(
const DeviceFiles::CdmUsageData& usage_data) {
if (!initialized_) {
LOGE("CdmSession::RestoreUsageSession: not initialized");
return NOT_INITIALIZED_ERROR;
}
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_ != NULL) {
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;
}
}
if (!license_parser_->RestoreLicenseForRelease(key_request_, key_response_)) {
return RELEASE_LICENSE_ERROR_2;
}
if (usage_support_type_ == kUsageEntrySupport &&
usage_table_header_ != NULL) {
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;
return KEY_ADDED;
}
// This is a thin wrapper that initiates the latency metric.
CdmResponseType CdmSession::GenerateKeyRequest(
const InitializationData& init_data, CdmLicenseType license_type,
const CdmAppParameterMap& app_parameters, CdmKeyRequest* key_request) {
CdmResponseType result = GenerateKeyRequestInternal(
init_data, license_type, app_parameters, key_request);
// Note that GenerateReleaseRequest and GenerateRenewalRequest will initialize
// the timer themselves. This is duplicate because there are duplicate paths
// for calling GenerateReleaseRequest and GenerateRenewalRequest.
if (result == KEY_MESSAGE) {
key_request_type_ = key_request->type;
license_request_latency_.Start(); // Start or restart timer.
}
return result;
}
CdmResponseType CdmSession::GenerateKeyRequestInternal(
const InitializationData& init_data, CdmLicenseType license_type,
const CdmAppParameterMap& app_parameters,
CdmKeyRequest* key_request) {
if (!initialized_) {
LOGE("CdmSession::GenerateKeyRequest: not initialized");
return NOT_INITIALIZED_ERROR;
}
if (!key_request) {
LOGE("CdmSession::GenerateKeyRequest: No output destination provided");
return PARAMETER_NULL;
}
switch (license_type) {
case kLicenseTypeTemporary:
is_temporary_ = true;
break;
case kLicenseTypeStreaming:
is_offline_ = false;
break;
case kLicenseTypeOffline:
is_offline_ = true;
break;
case kLicenseTypeRelease:
is_release_ = true;
break;
// TODO(jfore): CdmSession assumes a call to this method once a license has
// been received is a call to generate a license renewal message. Use of
// this enum differentiates the two calls. See "else if (license_received_)"
// below.
case kLicenseTypeEmbeddedKeyData:
return license_parser_->HandleEmbeddedKeyData(init_data);
default:
LOGE("CdmSession::GenerateKeyRequest: unrecognized license type: %ld",
license_type);
return INVALID_LICENSE_TYPE;
}
if (is_release_) {
return GenerateReleaseRequest(key_request);
} else if (license_received_) { // renewal
return GenerateRenewalRequest(key_request);
} else {
key_request->type = kKeyRequestTypeInitial;
if (!init_data.is_supported()) {
LOGW("CdmSession::GenerateKeyRequest: unsupported init data type (%s)",
init_data.type().c_str());
return UNSUPPORTED_INIT_DATA;
}
if (init_data.IsEmpty() && !license_parser_->HasInitData()) {
LOGW("CdmSession::GenerateKeyRequest: init data absent");
return INIT_DATA_NOT_FOUND;
}
if (is_offline_ && key_set_id_.empty()) {
LOGE("CdmSession::GenerateKeyRequest: Unable to generate key set ID");
return KEY_REQUEST_ERROR_1;
}
std::vector<video_widevine::SubLicense> embedded_key_data =
init_data.ExtractSublicenseKeys();
for (size_t i = 0; i < embedded_key_data.size(); ++i) {
CdmResponseType sts = crypto_session_->AddSubSession(
embedded_key_data[i].sub_session_key_id(),
embedded_key_data[i].group_id());
if (NO_ERROR != sts) {
LOGE("CdmSession::GenerateKeyRequest: Unable to generate sub session");
return sts;
}
}
app_parameters_ = app_parameters;
CdmResponseType status = license_parser_->PrepareKeyRequest(
init_data, license_type, app_parameters, &key_request->message,
&key_request->url);
if (status != KEY_MESSAGE) return status;
key_request_ = key_request->message;
if (is_offline_) {
offline_init_data_ = init_data.data();
offline_release_server_url_ = key_request->url;
}
return KEY_MESSAGE;
}
}
// This thin wrapper allows us to update metrics.
CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response) {
CdmResponseType sts = AddKeyInternal(key_response);
UpdateRequestLatencyTiming(sts);
return sts;
}
// AddKeyInternal() - Accept license response and extract key info.
CdmResponseType CdmSession::AddKeyInternal(const CdmKeyResponse& key_response) {
if (!initialized_) {
LOGE("CdmSession::AddKey: not initialized");
return NOT_INITIALIZED_ERROR;
}
if (is_release_) {
CdmResponseType sts = ReleaseKey(key_response);
return (sts == NO_ERROR) ? KEY_ADDED : sts;
} 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_ != NULL) {
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_ != NULL) {
if (sts != KEY_ADDED) {
CdmResponseType delete_sts = usage_table_header_->DeleteEntry(
usage_entry_number_, file_handle_.get(), crypto_metrics_);
if (delete_sts != NO_ERROR) {
LOGW("CdmSession::AddKey: Delete usage entry failed = %d",
delete_sts);
}
}
}
if (sts != KEY_ADDED) return (sts == KEY_ERROR) ? ADD_KEY_ERROR : sts;
license_received_ = true;
key_response_ = key_response;
LOGV("AddKey: provider_session_token (size=%d) =%s",
license_parser_->provider_session_token().size(),
license_parser_->provider_session_token().c_str());
if (is_offline_ || has_provider_session_token()) {
if (has_provider_session_token() &&
usage_support_type_ == kUsageEntrySupport &&
usage_table_header_ != NULL) {
usage_table_header_->UpdateEntry(crypto_session_.get(), &usage_entry_);
}
if (!is_offline_)
usage_provider_session_token_ =
license_parser_->provider_session_token();
sts = StoreLicense();
if (sts != NO_ERROR) return sts;
}
return KEY_ADDED;
}
}
CdmResponseType CdmSession::QueryStatus(CdmQueryMap* query_response) {
if (!initialized_) {
LOGE("CdmSession::QueryStatus: not initialized");
return NOT_INITIALIZED_ERROR;
}
switch (security_level_) {
case kSecurityLevelL1:
(*query_response)[QUERY_KEY_SECURITY_LEVEL] =
QUERY_VALUE_SECURITY_LEVEL_L1;
break;
case kSecurityLevelL2:
(*query_response)[QUERY_KEY_SECURITY_LEVEL] =
QUERY_VALUE_SECURITY_LEVEL_L2;
break;
case kSecurityLevelL3:
(*query_response)[QUERY_KEY_SECURITY_LEVEL] =
QUERY_VALUE_SECURITY_LEVEL_L3;
break;
case kSecurityLevelUninitialized:
case kSecurityLevelUnknown:
(*query_response)[QUERY_KEY_SECURITY_LEVEL] =
QUERY_VALUE_SECURITY_LEVEL_UNKNOWN;
break;
default:
return INVALID_QUERY_KEY;
}
return NO_ERROR;
}
CdmResponseType CdmSession::QueryKeyStatus(CdmQueryMap* query_response) {
return policy_engine_->Query(query_response);
}
CdmResponseType CdmSession::QueryKeyAllowedUsage(
const std::string& key_id, CdmKeyAllowedUsage* key_usage) {
return policy_engine_->QueryKeyAllowedUsage(key_id, key_usage);
}
CdmResponseType CdmSession::QueryOemCryptoSessionId(
CdmQueryMap* query_response) {
if (!initialized_) {
LOGE("CdmSession::QueryOemCryptoSessionId: not initialized");
return NOT_INITIALIZED_ERROR;
}
std::stringstream ss;
ss << crypto_session_->oec_session_id();
(*query_response)[QUERY_KEY_OEMCRYPTO_SESSION_ID] = ss.str();
return NO_ERROR;
}
// Decrypt() - Accept encrypted buffer and return decrypted data.
CdmResponseType CdmSession::Decrypt(const CdmDecryptionParameters& params) {
if (!initialized_) {
return NOT_INITIALIZED_ERROR;
}
// Encrypted playback may not begin until either the start time passes or the
// license is updated, so we treat this Decrypt call as invalid.
// For the clear lead, we allow playback even if the key_id is not found or if
// the security level is not high enough yet.
if (params.is_encrypted) {
if (!policy_engine_->CanDecryptContent(*params.key_id)) {
if (policy_engine_->IsLicenseForFuture()) return DECRYPT_NOT_READY;
if (!policy_engine_->IsSufficientOutputProtection(*params.key_id))
return INSUFFICIENT_OUTPUT_PROTECTION;
return NEED_KEY;
}
if (!policy_engine_->CanUseKeyForSecurityLevel(*params.key_id,
security_level_)) {
return KEY_PROHIBITED_FOR_SECURITY_LEVEL;
}
}
CdmResponseType status = crypto_session_->Decrypt(params);
if (status == NO_ERROR) {
if (is_initial_decryption_) {
policy_engine_->BeginDecryption();
is_initial_decryption_ = false;
}
has_decrypted_since_last_report_ = true;
if (!is_usage_update_needed_) {
is_usage_update_needed_ = has_provider_session_token();
}
} else {
Clock clock;
int64_t current_time = clock.GetCurrentTime();
if (policy_engine_->HasLicenseOrPlaybackDurationExpired(current_time)) {
return NEED_KEY;
}
}
return status;
}
// License renewal
// GenerateRenewalRequest() - Construct valid renewal request for the current
// session keys.
CdmResponseType CdmSession::GenerateRenewalRequest(CdmKeyRequest* key_request) {
if (!initialized_) {
LOGE("CdmSession::GenerateRenewalRequest: not initialized");
return NOT_INITIALIZED_ERROR;
}
CdmResponseType status = license_parser_->PrepareKeyUpdateRequest(
true, app_parameters_, NULL, &key_request->message, &key_request->url);
key_request->type = kKeyRequestTypeRenewal;
if (KEY_MESSAGE != status) return status;
if (is_offline_) {
offline_key_renewal_request_ = key_request->message;
}
key_request_type_ = key_request->type;
license_request_latency_.Start(); // Start or restart timer.
return KEY_MESSAGE;
}
// RenewKey() - Accept renewal response and update key info.
CdmResponseType CdmSession::RenewKey(const CdmKeyResponse& key_response) {
if (!initialized_) {
LOGE("CdmSession::RenewKey: not initialized");
return NOT_INITIALIZED_ERROR;
}
CdmResponseType sts =
license_parser_->HandleKeyUpdateResponse(true, key_response);
// Record the timing on success.
UpdateRequestLatencyTiming(sts);
if (sts != KEY_ADDED) return (sts == KEY_ERROR) ? RENEW_KEY_ERROR_1 : sts;
if (is_offline_) {
offline_key_renewal_response_ = key_response;
if (!StoreLicense(DeviceFiles::kLicenseStateActive))
return RENEW_KEY_ERROR_2;
}
return KEY_ADDED;
}
CdmResponseType CdmSession::GenerateReleaseRequest(CdmKeyRequest* key_request) {
if (!initialized_) {
LOGE("CdmSession::GenerateReleaseRequest: not initialized");
return NOT_INITIALIZED_ERROR;
}
is_release_ = true;
license_request_latency_.Clear();
CdmResponseType status = license_parser_->PrepareKeyUpdateRequest(
false, app_parameters_, usage_table_header_ == NULL ? NULL : this,
&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;
}
}
key_request_type_ = key_request->type;
license_request_latency_.Start(); // Start or restart timer.
return KEY_MESSAGE;
}
// ReleaseKey() - Accept release response and release license.
CdmResponseType CdmSession::ReleaseKey(const CdmKeyResponse& key_response) {
if (!initialized_) {
LOGE("CdmSession::ReleaseKey: not initialized");
return NOT_INITIALIZED_ERROR;
}
CdmResponseType sts =
license_parser_->HandleKeyUpdateResponse(false, key_response);
// Record the timing on success.
UpdateRequestLatencyTiming(sts);
if (sts != KEY_ADDED) return (sts == KEY_ERROR) ? RELEASE_KEY_ERROR : sts;
return RemoveLicense();
}
CdmResponseType CdmSession::DeleteUsageEntry(uint32_t usage_entry_number) {
if (!initialized_) {
LOGE("CdmSession::DeleteUsageEntry: not initialized");
return NOT_INITIALIZED_ERROR;
}
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(CryptoSession::MakeCryptoSession(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);
}
int64_t CdmSession::GetDurationRemaining() {
if (policy_engine_->IsLicenseForFuture()) return 0;
return policy_engine_->GetLicenseOrPlaybackDurationRemaining();
}
CdmSessionId CdmSession::GenerateSessionId() {
static int session_num = 1;
return SESSION_ID_PREFIX + IntToString(++session_num);
}
bool CdmSession::GenerateKeySetId(CdmKeySetId* key_set_id) {
if (!key_set_id) {
LOGW("CdmSession::GenerateKeySetId: key set id destination not provided");
return false;
}
std::vector<uint8_t> random_data(
(kKeySetIdLength - sizeof(KEY_SET_ID_PREFIX)) / 2, 0);
while (key_set_id->empty()) {
if (!crypto_session_->GetRandom(random_data.size(), &random_data[0])) {
return false;
}
*key_set_id = KEY_SET_ID_PREFIX + b2a_hex(random_data);
// key set collision
if (file_handle_->LicenseExists(*key_set_id)) {
key_set_id->clear();
}
}
// Reserve the license ID to avoid collisions.
file_handle_->ReserveLicenseId(*key_set_id);
return true;
}
CdmResponseType CdmSession::StoreLicense() {
if (is_temporary_) {
LOGE("CdmSession::StoreLicense: Session type prohibits storage.");
return STORAGE_PROHIBITED;
}
if (is_offline_) {
if (key_set_id_.empty()) {
LOGE("CdmSession::StoreLicense: No key set ID");
return EMPTY_KEYSET_ID;
}
if (!license_parser_->is_offline()) {
LOGE("CdmSession::StoreLicense: License policy prohibits storage.");
return OFFLINE_LICENSE_PROHIBITED;
}
if (!StoreLicense(DeviceFiles::kLicenseStateActive)) {
LOGE("CdmSession::StoreLicense: Unable to store license");
return STORE_LICENSE_ERROR_1;
}
return NO_ERROR;
} // if (is_offline_)
std::string provider_session_token =
license_parser_->provider_session_token();
if (provider_session_token.empty()) {
LOGE("CdmSession::StoreLicense: No provider session token and not offline");
return STORE_LICENSE_ERROR_2;
}
std::string app_id;
GetApplicationId(&app_id);
if (!file_handle_->StoreUsageInfo(
provider_session_token, key_request_, key_response_,
DeviceFiles::GetUsageInfoFileName(app_id), key_set_id_, usage_entry_,
usage_entry_number_)) {
LOGE("CdmSession::StoreLicense: Unable to store usage info");
// Usage info file is corrupt. Delete current usage entry and file.
switch (usage_support_type_) {
case kUsageEntrySupport:
DeleteUsageEntry(usage_entry_number_);
break;
case kUsageTableSupport:
crypto_session_->DeleteUsageInformation(provider_session_token);
crypto_session_->UpdateUsageInformation();
break;
default:
LOGW("CdmSession::StoreLicense: unexpected usage support type: %d",
usage_support_type_);
break;
}
std::vector<std::string> provider_session_tokens;
file_handle_->DeleteAllUsageInfoForApp(
DeviceFiles::GetUsageInfoFileName(app_id),
&provider_session_tokens);
return STORE_USAGE_INFO_ERROR;
}
return NO_ERROR;
}
bool CdmSession::StoreLicense(DeviceFiles::LicenseState state) {
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_);
}
CdmResponseType CdmSession::RemoveKeys() {
CdmResponseType sts;
crypto_session_.reset(CryptoSession::MakeCryptoSession(crypto_metrics_));
// Ignore errors
M_TIME(
sts = crypto_session_->Open(requested_security_level_),
crypto_metrics_,
crypto_session_open_,
sts,
requested_security_level_);
policy_engine_.reset(new PolicyEngine(
session_id_, NULL, crypto_session_.get()));
return NO_ERROR;
}
CdmResponseType CdmSession::RemoveLicense() {
if (is_offline_ || has_provider_session_token()) {
DeleteLicenseFile();
if (usage_support_type_ == kUsageEntrySupport &&
has_provider_session_token()) {
CdmResponseType sts = DeleteUsageEntry(usage_entry_number_);
if (NO_ERROR != sts) return sts;
}
}
return NO_ERROR;
}
bool CdmSession::DeleteLicenseFile() {
if (!is_offline_ && !has_provider_session_token()) return false;
if (is_offline_) {
return file_handle_->DeleteLicense(key_set_id_);
} else {
std::string app_id;
GetApplicationId(&app_id);
return file_handle_->DeleteUsageInfo(
DeviceFiles::GetUsageInfoFileName(app_id),
license_parser_->provider_session_token());
}
}
void CdmSession::NotifyResolution(uint32_t width, uint32_t height) {
policy_engine_->NotifyResolution(width, height);
}
void CdmSession::OnTimerEvent(bool update_usage) {
if (update_usage && has_decrypted_since_last_report_) {
policy_engine_->DecryptionEvent();
has_decrypted_since_last_report_ = false;
if (is_offline_ && !is_release_) {
StoreLicense(DeviceFiles::kLicenseStateActive);
}
}
policy_engine_->OnTimerEvent();
}
void CdmSession::OnKeyReleaseEvent(const CdmKeySetId& key_set_id) {
if (key_set_id_ == key_set_id) {
policy_engine_->NotifySessionExpiration();
}
}
void CdmSession::GetApplicationId(std::string* app_id) {
if (app_id && !Properties::GetApplicationId(session_id_, app_id)) {
*app_id = "";
}
}
CdmResponseType CdmSession::DeleteMultipleUsageInformation(
const std::vector<std::string>& provider_session_tokens) {
CdmUsageSupportType usage_support_type;
CdmResponseType sts =
crypto_session_->GetUsageSupportType(&usage_support_type);
if (sts == NO_ERROR && usage_support_type == kUsageTableSupport) {
for (size_t i = 0; i < provider_session_tokens.size(); ++i) {
crypto_session_->DeactivateUsageInformation(provider_session_tokens[i]);
UpdateUsageTableInformation();
}
}
if (sts == NO_ERROR) {
sts = crypto_session_->DeleteMultipleUsageInformation(
provider_session_tokens);
crypto_metrics_->crypto_session_delete_multiple_usage_information_
.Increment(sts);
}
return sts;
}
CdmResponseType CdmSession::UpdateUsageTableInformation() {
CdmUsageSupportType usage_support_type;
CdmResponseType sts =
crypto_session_->GetUsageSupportType(&usage_support_type);
if (sts == NO_ERROR && usage_support_type == kUsageTableSupport) {
M_TIME(sts = crypto_session_->UpdateUsageInformation(), crypto_metrics_,
crypto_session_update_usage_information_, sts);
return sts;
}
return NO_ERROR; // Ignore
}
CdmResponseType CdmSession::UpdateUsageEntryInformation() {
if (usage_support_type_ != kUsageEntrySupport ||
!has_provider_session_token() || usage_table_header_ == NULL) {
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_ == NULL ? "no" : "yes");
return INCORRECT_USAGE_SUPPORT_TYPE_2;
}
CdmResponseType sts = NO_ERROR;
M_TIME(
sts = usage_table_header_->UpdateEntry(crypto_session_.get(),
&usage_entry_),
crypto_metrics_,
crypto_session_update_usage_entry_,
sts);
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::GenericEncrypt(const std::string& in_buffer,
const std::string& key_id,
const std::string& iv,
CdmEncryptionAlgorithm algorithm,
std::string* out_buffer) {
if (!out_buffer) {
LOGE("CdmSession::GenericEncrypt: No output destination provided");
return PARAMETER_NULL;
}
CdmResponseType sts;
M_TIME(sts = crypto_session_->GenericEncrypt(in_buffer, key_id, iv, algorithm,
out_buffer),
crypto_metrics_, crypto_session_generic_encrypt_, sts,
metrics::Pow2Bucket(in_buffer.size()), algorithm);
return sts;
}
CdmResponseType CdmSession::GenericDecrypt(const std::string& in_buffer,
const std::string& key_id,
const std::string& iv,
CdmEncryptionAlgorithm algorithm,
std::string* out_buffer) {
if (!out_buffer) {
LOGE("CdmSession::GenericDecrypt: No output destination provided");
return PARAMETER_NULL;
}
CdmResponseType sts;
M_TIME(sts = crypto_session_->GenericDecrypt(in_buffer, key_id, iv, algorithm,
out_buffer),
crypto_metrics_, crypto_session_generic_decrypt_, sts,
metrics::Pow2Bucket(in_buffer.size()), algorithm);
return sts;
}
CdmResponseType CdmSession::GenericSign(const std::string& message,
const std::string& key_id,
CdmSigningAlgorithm algorithm,
std::string* signature) {
if (!signature) {
LOGE("CdmSession::GenericSign: No output destination provided");
return PARAMETER_NULL;
}
CdmResponseType sts;
M_TIME(
sts = crypto_session_->GenericSign(message, key_id, algorithm, signature),
crypto_metrics_, crypto_session_generic_sign_, sts,
metrics::Pow2Bucket(message.size()), algorithm);
return sts;
}
CdmResponseType CdmSession::GenericVerify(const std::string& message,
const std::string& key_id,
CdmSigningAlgorithm algorithm,
const std::string& signature) {
CdmResponseType sts;
M_TIME(sts = crypto_session_->GenericVerify(message, key_id, algorithm,
signature),
crypto_metrics_, crypto_session_generic_verify_, sts,
metrics::Pow2Bucket(message.size()), algorithm);
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);
}
void CdmSession::UpdateRequestLatencyTiming(CdmResponseType sts) {
if (sts == KEY_ADDED && license_request_latency_.IsStarted()) {
metrics_->cdm_session_license_request_latency_ms_.Record(
license_request_latency_.AsMs(), key_request_type_);
}
license_request_latency_.Clear();
}
// For testing only - takes ownership of pointers
void CdmSession::set_license_parser(CdmLicense* license_parser) {
license_parser_.reset(license_parser);
mock_license_parser_in_use_ = true;
}
void CdmSession::set_crypto_session(CryptoSession* crypto_session) {
crypto_session_.reset(crypto_session);
}
void CdmSession::set_policy_engine(PolicyEngine* policy_engine) {
policy_engine_.reset(policy_engine);
mock_policy_engine_in_use_ = true;
}
void CdmSession::set_file_handle(DeviceFiles* file_handle) {
file_handle_.reset(file_handle);
}
} // namespace wvcdm