[ Merge of http://go/wvgerrit/70543 ] RestoreOfflineLicense and RestoreLicenseForRelease now return CdmResponseType errors rather than a boolean. These error codes can now be used when gathering metrics. Bug: 115517916 Test: WV unit/integration tests Change-Id: If4784d2cdd9825948c5dec31d3e60058ea06b61d
1134 lines
37 KiB
C++
1134 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 &&
|
|
!is_release_) {
|
|
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_);
|
|
std::string oemcrypto_build;
|
|
if(crypto_session_->GetBuildInformation(requested_security_level_,
|
|
&oemcrypto_build)) {
|
|
metrics_->oemcrypto_build_info_.Record(oemcrypto_build);
|
|
} else {
|
|
metrics_->oemcrypto_build_info_.SetError(false);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
CdmResponseType result;
|
|
if (license_type == kLicenseTypeRelease) {
|
|
result =
|
|
license_parser_->RestoreLicenseForRelease(key_request_, key_response_);
|
|
|
|
if (result != NO_ERROR) {
|
|
return RELEASE_LICENSE_ERROR_1;
|
|
}
|
|
} else {
|
|
result = license_parser_->RestoreOfflineLicense(
|
|
key_request_, key_response_, offline_key_renewal_response_,
|
|
playback_start_time, last_playback_time, grace_period_end_time, this);
|
|
if (result != NO_ERROR) {
|
|
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;
|
|
|
|
CdmResponseType sts = NO_ERROR;
|
|
if (usage_support_type_ == kUsageEntrySupport &&
|
|
usage_table_header_ != NULL) {
|
|
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;
|
|
}
|
|
}
|
|
|
|
sts = license_parser_->RestoreLicenseForRelease(key_request_, key_response_);
|
|
|
|
if (sts != NO_ERROR) {
|
|
return RELEASE_LICENSE_ERROR_2;
|
|
}
|
|
|
|
if (usage_support_type_ == kUsageEntrySupport &&
|
|
usage_table_header_ != NULL) {
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
CdmResponseType CdmSession::SetDecryptHash(uint32_t frame_number,
|
|
const std::string& hash) {
|
|
return crypto_session_->SetDecryptHash(frame_number, hash);
|
|
}
|
|
|
|
CdmResponseType CdmSession::GetDecryptHashError(
|
|
std::string* error_string) {
|
|
return crypto_session_->GetDecryptHashError(error_string);
|
|
}
|
|
|
|
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
|