[ Merge of http://go/wvgerrit/165617 ] Similar to the issue with updating secure stops by PST (see http://go/wvgerrit/165597), when deleting different secure stops with the same PST results in unintended behavior. This CL changes how the CDM identifies which secure stop to delete from storaged based on the key set ID rather than the PST. Bug: 263316107 Test: device_files_unittest Test: GTS MediaDrmParameterizedTests and MediaDrmStressTest Change-Id: Ic3843a1435f252f052c7189423c211c28ed74eaa
1266 lines
45 KiB
C++
1266 lines
45 KiB
C++
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
|
// source code may only be used and distributed under the Widevine License
|
|
// Agreement.
|
|
|
|
#include "cdm_session.h"
|
|
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <algorithm>
|
|
#include <iterator>
|
|
#include <string>
|
|
|
|
#include "cdm_engine.h"
|
|
#include "cdm_random.h"
|
|
#include "cdm_usage_table.h"
|
|
#include "clock.h"
|
|
#include "crypto_wrapped_key.h"
|
|
#include "file_store.h"
|
|
#include "log.h"
|
|
#include "properties.h"
|
|
#include "string_conversions.h"
|
|
#include "wv_cdm_constants.h"
|
|
#include "wv_cdm_event_listener.h"
|
|
|
|
// Stringify turns macro arguments into static C strings.
|
|
// Example: STRINGIFY(this_argument) -> "this_argument"
|
|
#define STRINGIFY(PARAM) #PARAM
|
|
|
|
#define RETURN_STATUS_IF_NULL(PARAM) \
|
|
if ((PARAM) == nullptr) { \
|
|
LOGE("Output parameter |" STRINGIFY(PARAM) "| not provided"); \
|
|
return CdmResponseType(PARAMETER_NULL); \
|
|
}
|
|
|
|
#define RETURN_FALSE_IF_NULL(PARAM) \
|
|
if ((PARAM) == nullptr) { \
|
|
LOGE("Output parameter |" STRINGIFY(PARAM) "| not provided"); \
|
|
return false; \
|
|
}
|
|
|
|
namespace wvcdm {
|
|
namespace {
|
|
const size_t kKeySetIdLength = 14;
|
|
|
|
// Helper function for setting the error detail value.
|
|
template <typename T>
|
|
void SetErrorDetail(int* error_detail, T error_code) {
|
|
if (error_detail != nullptr) {
|
|
*error_detail = static_cast<int>(error_code);
|
|
}
|
|
}
|
|
|
|
int DrmKeyTypeToMetricValue(CryptoWrappedKey::Type type) {
|
|
constexpr int kUnknownMetricType = -1;
|
|
constexpr int kRsaMetricType = 0;
|
|
constexpr int kEccMetricType = 1;
|
|
switch (type) {
|
|
case CryptoWrappedKey::kRsa:
|
|
return kRsaMetricType;
|
|
case CryptoWrappedKey::kEcc:
|
|
return kEccMetricType;
|
|
default:
|
|
LOGE("Unexpected DRM key type: %d", static_cast<int>(type));
|
|
return kUnknownMetricType;
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
CdmSession::CdmSession(wvutil::FileSystem* file_system,
|
|
std::shared_ptr<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_usage_update_(true),
|
|
is_usage_update_needed_(false),
|
|
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 (has_provider_session_token() && SupportsUsageTable() && !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_.get(), cdm_session_life_span_, life_span_.AsMs());
|
|
metrics_->SetCompleted();
|
|
}
|
|
}
|
|
|
|
CdmResponseType CdmSession::Init(
|
|
CdmClientPropertySet* cdm_client_property_set) {
|
|
return Init(cdm_client_property_set, nullptr, nullptr, false);
|
|
}
|
|
|
|
CdmResponseType CdmSession::Init(CdmClientPropertySet* cdm_client_property_set,
|
|
const CdmSessionId* forced_session_id,
|
|
WvCdmEventListener* event_listener,
|
|
bool forced_level3) {
|
|
if (initialized_) {
|
|
LOGE("Failed due to previous initialization");
|
|
return CdmResponseType(REINIT_ERROR);
|
|
}
|
|
|
|
if ((cdm_client_property_set && cdm_client_property_set->security_level() ==
|
|
QUERY_VALUE_SECURITY_LEVEL_L3) ||
|
|
forced_level3) {
|
|
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("Unable to initialize file handle");
|
|
return CdmResponseType(SESSION_FILE_HANDLE_INIT_ERROR);
|
|
}
|
|
|
|
bool has_support = false;
|
|
if (crypto_session_->HasUsageTableSupport(&has_support) && has_support) {
|
|
usage_table_ = crypto_session_->GetUsageTable();
|
|
}
|
|
|
|
if (cdm_client_property_set != nullptr)
|
|
atsc_mode_enabled_ = cdm_client_property_set->use_atsc_mode();
|
|
|
|
// If a DRM certificate does not exist, indicate that provisioning is needed.
|
|
// The actual validation and loading of a certificate will happen when
|
|
// a key request is generated or an offline license is loaded.
|
|
if (!file_handle_->HasCertificate(atsc_mode_enabled_))
|
|
return CdmResponseType(NEED_PROVISIONING);
|
|
|
|
if (forced_session_id) {
|
|
key_set_id_ = *forced_session_id;
|
|
} else {
|
|
const bool ok = GenerateKeySetId(atsc_mode_enabled_, &key_set_id_);
|
|
assert(ok);
|
|
if (!ok) {
|
|
// Assertions may be disabled
|
|
LOGE("Could not generate keyset ID");
|
|
}
|
|
}
|
|
|
|
session_id_ =
|
|
Properties::AlwaysUseKeySetIds() ? key_set_id_ : GenerateSessionId();
|
|
metrics_->SetSessionId(session_id_);
|
|
|
|
if (session_id_.empty()) {
|
|
LOGE("Empty session ID");
|
|
return CdmResponseType(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(Properties::UsePrivacyMode(session_id_),
|
|
service_certificate, crypto_session_.get(),
|
|
policy_engine_.get()))
|
|
return CdmResponseType(LICENSE_PARSER_INIT_ERROR);
|
|
|
|
license_received_ = false;
|
|
is_initial_decryption_ = true;
|
|
initialized_ = true;
|
|
closed_ = false;
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
|
|
CdmResponseType CdmSession::RestoreOfflineSession(const CdmKeySetId& key_set_id,
|
|
CdmLicenseType license_type,
|
|
int* error_detail) {
|
|
if (!initialized_) {
|
|
LOGE("CDM session not initialized");
|
|
return CdmResponseType(NOT_INITIALIZED_ERROR);
|
|
}
|
|
if (!key_set_id_.empty()) {
|
|
file_handle_->UnreserveLicenseId(key_set_id_);
|
|
}
|
|
if (has_license_been_loaded_ || has_license_been_restored_) {
|
|
LOGE(
|
|
"Disallow multiple offline license restores or restoring a license if "
|
|
"a license has already been loaded");
|
|
return CdmResponseType(RESTORE_OFFLINE_LICENSE_ERROR_3);
|
|
}
|
|
|
|
key_set_id_ = key_set_id;
|
|
|
|
DeviceFiles::CdmLicenseData license_data;
|
|
DeviceFiles::ResponseType sub_error_code = DeviceFiles::kNoError;
|
|
|
|
if (!file_handle_->RetrieveLicense(key_set_id, &license_data,
|
|
&sub_error_code)) {
|
|
LOGE("Failed to retrieve license: sub_error_code = %s, key_set_id = %s",
|
|
DeviceFiles::ResponseTypeToString(sub_error_code),
|
|
IdToString(key_set_id));
|
|
SetErrorDetail(error_detail, sub_error_code);
|
|
return sub_error_code == DeviceFiles::kFileNotFound
|
|
? CdmResponseType(KEYSET_ID_NOT_FOUND_4)
|
|
: CdmResponseType(GET_LICENSE_ERROR);
|
|
}
|
|
offline_init_data_ = std::move(license_data.pssh_data);
|
|
key_request_ = std::move(license_data.license_request);
|
|
key_response_ = std::move(license_data.license);
|
|
offline_key_renewal_request_ =
|
|
std::move(license_data.license_renewal_request);
|
|
offline_key_renewal_response_ = std::move(license_data.license_renewal);
|
|
offline_release_server_url_ = std::move(license_data.release_server_url);
|
|
app_parameters_ = std::move(license_data.app_parameters);
|
|
usage_entry_ = std::move(license_data.usage_entry);
|
|
usage_entry_index_ = license_data.usage_entry_index;
|
|
|
|
CdmResponseType result = LoadPrivateOrLegacyKey(
|
|
license_data.drm_certificate, license_data.wrapped_private_key);
|
|
if (result != NO_ERROR) return result;
|
|
|
|
// Attempts to restore a released offline license are treated as a release
|
|
// retry.
|
|
if (Properties::allow_restore_of_offline_licenses_with_release()) {
|
|
if (license_data.state == kLicenseStateReleasing) {
|
|
license_type = kLicenseTypeRelease;
|
|
}
|
|
}
|
|
|
|
// Only restore offline licenses if they are active or this is a release
|
|
// retry.
|
|
if (!(license_type == kLicenseTypeRelease ||
|
|
license_data.state == kLicenseStateActive)) {
|
|
LOGE("Invalid offline license state: state = %s, license_type = %s",
|
|
CdmOfflineLicenseStateToString(license_data.state),
|
|
CdmLicenseTypeToString(license_type));
|
|
return CdmResponseType(GET_RELEASED_LICENSE_ERROR);
|
|
}
|
|
|
|
std::string provider_session_token;
|
|
bool sign_fake_request = false; // TODO(b/169483174): remove this variable.
|
|
if (SupportsUsageTable()) {
|
|
if (!license_parser_->ExtractProviderSessionToken(
|
|
key_response_, &provider_session_token)) {
|
|
provider_session_token.clear();
|
|
sign_fake_request = true; // TODO(b/169483174): remove this line.
|
|
} else if (!VerifyOfflineUsageEntry()) {
|
|
LOGE("License usage entry is invalid, cannot restore");
|
|
return CdmResponseType(LICENSE_USAGE_ENTRY_MISSING);
|
|
} else {
|
|
CdmResponseType sts = usage_table_->LoadEntry(
|
|
crypto_session_.get(), usage_entry_, usage_entry_index_);
|
|
crypto_metrics_->usage_table_header_load_entry_.Increment(sts);
|
|
if (sts == LOAD_USAGE_ENTRY_INVALID_SESSION) {
|
|
LOGE("License loaded in different session: key_set_id = %s",
|
|
IdToString(key_set_id));
|
|
return CdmResponseType(USAGE_ENTRY_ALREADY_LOADED);
|
|
}
|
|
if (sts != NO_ERROR) {
|
|
LOGE("Failed to load usage entry: status = %d", static_cast<int>(sts));
|
|
return sts;
|
|
}
|
|
}
|
|
} else {
|
|
sign_fake_request = true; // TODO(b/169483174): remove this block.
|
|
}
|
|
// TODO(b/169483174): remove this code in v17. For OEMCrypto v16, an offline
|
|
// license would not work because the rental clock in OEMCrypto is only
|
|
// started when the license request is signed. We will sign a fake license
|
|
// request if the device does not support usage tables, or if the license does
|
|
// not have a usage entry.
|
|
if (sign_fake_request) {
|
|
std::string fake_message("empty message");
|
|
std::string core_message;
|
|
std::string license_request_signature;
|
|
bool should_specify_algorithm;
|
|
OEMCrypto_SignatureHashAlgorithm algorithm = OEMCrypto_SHA1;
|
|
uint32_t nonce;
|
|
// Sign a fake message so that OEMCrypto will start the rental clock. The
|
|
// signature and generated core message are ignored.
|
|
result = crypto_session_->GenerateNonce(&nonce);
|
|
if (result != NO_ERROR) return result;
|
|
result = crypto_session_->PrepareAndSignLicenseRequest(
|
|
fake_message, &core_message, &license_request_signature,
|
|
should_specify_algorithm, algorithm);
|
|
if (result != NO_ERROR) return result;
|
|
}
|
|
|
|
if (license_type == kLicenseTypeRelease) {
|
|
result = license_parser_->RestoreLicenseForRelease(
|
|
license_data.drm_certificate, key_request_, key_response_);
|
|
|
|
if (result != NO_ERROR) {
|
|
SetErrorDetail(error_detail, result.Enum());
|
|
return CdmResponseType(RELEASE_LICENSE_ERROR_1);
|
|
}
|
|
} else {
|
|
result = license_parser_->RestoreOfflineLicense(
|
|
license_data.drm_certificate, key_request_, key_response_,
|
|
offline_key_renewal_response_, license_data.playback_start_time,
|
|
license_data.last_playback_time, license_data.grace_period_end_time,
|
|
this);
|
|
if (result != NO_ERROR) {
|
|
SetErrorDetail(error_detail, result.Enum());
|
|
return CdmResponseType(RESTORE_OFFLINE_LICENSE_ERROR_2);
|
|
}
|
|
}
|
|
|
|
if (!provider_session_token.empty() && SupportsUsageTable()) {
|
|
CdmResponseType sts = usage_table_->UpdateEntry(
|
|
usage_entry_index_, crypto_session_.get(), &usage_entry_);
|
|
if (sts != NO_ERROR) {
|
|
LOGE("Failed to update usage entry: status = %d", static_cast<int>(sts));
|
|
return sts;
|
|
}
|
|
if (!StoreLicense(license_data.state, error_detail)) {
|
|
LOGW("Unable to save updated usage info");
|
|
}
|
|
}
|
|
|
|
license_received_ = true;
|
|
is_offline_ = true;
|
|
is_release_ = license_type == kLicenseTypeRelease;
|
|
has_license_been_restored_ = true;
|
|
return CdmResponseType(KEY_ADDED);
|
|
}
|
|
|
|
CdmResponseType CdmSession::RestoreUsageSession(
|
|
const DeviceFiles::CdmUsageData& usage_data, int* error_detail) {
|
|
if (!initialized_) {
|
|
LOGE("CDM session not initialized");
|
|
return CdmResponseType(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_index_ = usage_data.usage_entry_index;
|
|
usage_provider_session_token_ = usage_data.provider_session_token;
|
|
|
|
CdmResponseType status = LoadPrivateOrLegacyKey(
|
|
usage_data.drm_certificate, usage_data.wrapped_private_key);
|
|
if (status != NO_ERROR) return status;
|
|
|
|
CdmResponseType sts(NO_ERROR);
|
|
if (SupportsUsageTable()) {
|
|
sts = usage_table_->LoadEntry(crypto_session_.get(), usage_entry_,
|
|
usage_entry_index_);
|
|
crypto_metrics_->usage_table_header_load_entry_.Increment(sts);
|
|
if (sts != NO_ERROR) {
|
|
LOGE("Failed to load usage entry: status = %d", static_cast<int>(sts));
|
|
return sts;
|
|
}
|
|
}
|
|
|
|
sts = license_parser_->RestoreLicenseForRelease(usage_data.drm_certificate,
|
|
key_request_, key_response_);
|
|
|
|
if (sts != NO_ERROR) {
|
|
SetErrorDetail(error_detail, sts);
|
|
return CdmResponseType(RELEASE_LICENSE_ERROR_2);
|
|
}
|
|
|
|
if (SupportsUsageTable()) {
|
|
sts = usage_table_->UpdateEntry(usage_entry_index_, crypto_session_.get(),
|
|
&usage_entry_);
|
|
if (sts != NO_ERROR) {
|
|
LOGE("Failed to update usage entry: status = %d", static_cast<int>(sts));
|
|
return sts;
|
|
}
|
|
if (!UpdateUsageInfo()) {
|
|
LOGW("Unable to save updated usage info");
|
|
}
|
|
}
|
|
|
|
license_received_ = true;
|
|
is_offline_ = false;
|
|
is_release_ = true;
|
|
return CdmResponseType(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("CDM session not initialized");
|
|
return CdmResponseType(NOT_INITIALIZED_ERROR);
|
|
}
|
|
RETURN_STATUS_IF_NULL(key_request);
|
|
|
|
switch (license_type) {
|
|
case kLicenseTypeTemporary:
|
|
is_temporary_ = true;
|
|
is_offline_ = false;
|
|
break;
|
|
case kLicenseTypeStreaming:
|
|
is_offline_ = false;
|
|
break;
|
|
case kLicenseTypeOffline:
|
|
is_offline_ = true;
|
|
break;
|
|
case kLicenseTypeRelease:
|
|
is_release_ = true;
|
|
break;
|
|
// TODO(b/132071885): Once a license has been received, CdmSession assumes
|
|
// that a call to this method is to generate a license renewal/release
|
|
// or key rotation. Key rotation can be indicated by specifing a license
|
|
// type kLicenseTypeEmbeddedKeyData or interrogating the PSSH
|
|
// (See "else if (license_received_)" below). b/132071885 is to evaluate
|
|
// whether both mechanisms are needed.
|
|
case kLicenseTypeEmbeddedKeyData:
|
|
return license_parser_->HandleEmbeddedKeyData(init_data);
|
|
default:
|
|
LOGE("Unrecognized license type: %d", static_cast<int>(license_type));
|
|
return CdmResponseType(INVALID_LICENSE_TYPE);
|
|
}
|
|
|
|
if (is_release_) {
|
|
return GenerateReleaseRequest(key_request);
|
|
}
|
|
if (license_received_) {
|
|
// A call to GenerateKeyRequest after the initial license has been received
|
|
// is either a renewal/release request or a key rotation event
|
|
if (init_data.contains_entitled_keys()) {
|
|
key_request->message.clear();
|
|
key_request->type = kKeyRequestTypeNone;
|
|
key_request->url.clear();
|
|
return license_parser_->HandleEmbeddedKeyData(init_data);
|
|
}
|
|
return GenerateRenewalRequest(key_request);
|
|
}
|
|
// Otherwise, initialize license request.
|
|
key_request->type = kKeyRequestTypeInitial;
|
|
|
|
if (!init_data.is_supported()) {
|
|
LOGW("Unsupported init data type: %s", init_data.type().c_str());
|
|
return CdmResponseType(UNSUPPORTED_INIT_DATA);
|
|
}
|
|
if (init_data.IsEmpty() && !license_parser_->HasInitData()) {
|
|
LOGW("Init data absent");
|
|
return CdmResponseType(INIT_DATA_NOT_FOUND);
|
|
}
|
|
if (is_offline_ && key_set_id_.empty()) {
|
|
LOGE("Key set ID not set");
|
|
return CdmResponseType(KEY_REQUEST_ERROR_1);
|
|
}
|
|
// Attempt to load provisioned private key if available.
|
|
CdmResponseType status = LoadPrivateKey();
|
|
if (status != NO_ERROR) return status;
|
|
|
|
app_parameters_ = app_parameters;
|
|
status = license_parser_->PrepareKeyRequest(
|
|
init_data, drm_certificate_, 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 CdmResponseType(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("Not initialized");
|
|
return CdmResponseType(NOT_INITIALIZED_ERROR);
|
|
}
|
|
|
|
if (is_release_) {
|
|
const CdmResponseType sts = ReleaseKey(key_response);
|
|
return (sts == NO_ERROR) ? CdmResponseType(KEY_ADDED) : sts;
|
|
}
|
|
if (license_received_) { // renewal
|
|
return RenewKey(key_response);
|
|
}
|
|
// 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 (SupportsUsageTable()) {
|
|
if (license_parser_->ExtractProviderSessionToken(key_response,
|
|
&provider_session_token) &&
|
|
!provider_session_token.empty()) {
|
|
std::string app_id;
|
|
GetApplicationId(&app_id);
|
|
sts = usage_table_->AddEntry(crypto_session_.get(), is_offline_,
|
|
key_set_id_,
|
|
DeviceFiles::GetUsageInfoFileName(app_id),
|
|
key_response, &usage_entry_index_);
|
|
crypto_metrics_->usage_table_header_add_entry_.Increment(sts);
|
|
if (sts != NO_ERROR) return sts;
|
|
}
|
|
}
|
|
sts =
|
|
license_parser_->HandleKeyResponse(/* is restore */ false, key_response);
|
|
|
|
// Update the license sdk and service versions.
|
|
const VersionInfo& version_info = license_parser_->GetServiceVersion();
|
|
metrics_->license_sdk_version_.Record(version_info.license_sdk_version());
|
|
metrics_->license_service_version_.Record(
|
|
version_info.license_service_version());
|
|
|
|
// Update or invalidate entry if usage table header+entries are supported
|
|
if (!provider_session_token.empty() && SupportsUsageTable()) {
|
|
if (sts != KEY_ADDED) {
|
|
const CdmResponseType invalidate_sts = usage_table_->InvalidateEntry(
|
|
usage_entry_index_, true, file_handle_.get(), crypto_metrics_);
|
|
crypto_metrics_->usage_table_header_delete_entry_.Increment(
|
|
invalidate_sts);
|
|
if (invalidate_sts != NO_ERROR) {
|
|
LOGW("Invalidate usage entry failed: status = %d",
|
|
static_cast<int>(invalidate_sts));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sts != KEY_ADDED)
|
|
return (sts == KEY_ERROR) ? CdmResponseType(ADD_KEY_ERROR) : sts;
|
|
|
|
license_received_ = true;
|
|
key_response_ = key_response;
|
|
|
|
LOGV("Key added: provider_session_token = %s (size = %zu)",
|
|
IdToString(license_parser_->provider_session_token()),
|
|
license_parser_->provider_session_token().size());
|
|
|
|
if ((is_offline_ || has_provider_session_token()) && !is_temporary_) {
|
|
if (has_provider_session_token() && SupportsUsageTable()) {
|
|
usage_table_->UpdateEntry(usage_entry_index_, 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;
|
|
}
|
|
|
|
has_license_been_loaded_ = true;
|
|
return CdmResponseType(KEY_ADDED);
|
|
}
|
|
|
|
CdmResponseType CdmSession::QueryStatus(CdmQueryMap* query_response) {
|
|
if (!initialized_) {
|
|
LOGE("CDM session not initialized");
|
|
return CdmResponseType(NOT_INITIALIZED_ERROR);
|
|
}
|
|
RETURN_STATUS_IF_NULL(query_response);
|
|
|
|
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 CdmResponseType(INVALID_QUERY_KEY);
|
|
}
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
|
|
CdmResponseType CdmSession::SetServiceCertificate(
|
|
const std::string& service_certificate) {
|
|
return license_parser_->SetServiceCertificate(service_certificate);
|
|
}
|
|
|
|
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("CDM session not initialized");
|
|
return CdmResponseType(NOT_INITIALIZED_ERROR);
|
|
}
|
|
RETURN_STATUS_IF_NULL(query_response);
|
|
|
|
(*query_response)[QUERY_KEY_OEMCRYPTO_SESSION_ID] =
|
|
std::to_string(crypto_session_->oec_session_id());
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
|
|
// Decrypt() - Accept encrypted buffer and return decrypted data.
|
|
CdmResponseType CdmSession::Decrypt(const CdmDecryptionParametersV16& params) {
|
|
if (!initialized_) {
|
|
return CdmResponseType(NOT_INITIALIZED_ERROR);
|
|
}
|
|
|
|
bool is_protected = std::any_of(
|
|
std::begin(params.samples), std::end(params.samples),
|
|
[](const CdmDecryptionSample& sample) {
|
|
return std::any_of(std::begin(sample.subsamples),
|
|
std::end(sample.subsamples),
|
|
[](const CdmDecryptionSubsample& subsample) {
|
|
return subsample.protected_bytes > 0;
|
|
});
|
|
});
|
|
|
|
// Protected 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 (is_protected) {
|
|
if (!policy_engine_->CanDecryptContent(params.key_id)) {
|
|
if (policy_engine_->IsLicenseForFuture())
|
|
return CdmResponseType(DECRYPT_NOT_READY);
|
|
if (!policy_engine_->IsSufficientOutputProtection(params.key_id)) {
|
|
LOGE("Key use prohibited as HDCP or resolution requirements not met");
|
|
return CdmResponseType(INSUFFICIENT_OUTPUT_PROTECTION);
|
|
}
|
|
return CdmResponseType(NEED_KEY);
|
|
}
|
|
if (!policy_engine_->CanUseKeyForSecurityLevel(params.key_id)) {
|
|
LOGE(
|
|
"Key use prohibited as security level requirements in the policy"
|
|
" not met");
|
|
return CdmResponseType(KEY_PROHIBITED_FOR_SECURITY_LEVEL);
|
|
}
|
|
}
|
|
|
|
const CdmResponseType status = crypto_session_->Decrypt(params);
|
|
|
|
if (status == NO_ERROR) {
|
|
if (is_initial_decryption_) {
|
|
is_initial_decryption_ = !policy_engine_->BeginDecryption();
|
|
}
|
|
has_decrypted_since_last_report_ = true;
|
|
if (!is_usage_update_needed_) {
|
|
is_usage_update_needed_ = has_provider_session_token();
|
|
}
|
|
last_decrypt_failed_ = false;
|
|
} else {
|
|
if (!last_decrypt_failed_) {
|
|
// Only log on the first failure. Certain failures are likely
|
|
// to occur in succession of each other.
|
|
LOGE("Decryption failed: sid = %s, status = %d", IdToString(session_id_),
|
|
static_cast<int>(status));
|
|
}
|
|
last_decrypt_failed_ = true;
|
|
wvutil::Clock clock;
|
|
const int64_t current_time = clock.GetCurrentTime();
|
|
if (policy_engine_->HasLicenseOrRentalOrPlaybackDurationExpired(
|
|
current_time)) {
|
|
return CdmResponseType(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("CDM session not initialized");
|
|
return CdmResponseType(NOT_INITIALIZED_ERROR);
|
|
}
|
|
RETURN_STATUS_IF_NULL(key_request);
|
|
|
|
CdmResponseType status = license_parser_->PrepareKeyUpdateRequest(
|
|
true, app_parameters_, nullptr, &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 CdmResponseType(KEY_MESSAGE);
|
|
}
|
|
|
|
// RenewKey() - Accept renewal response and update key info.
|
|
CdmResponseType CdmSession::RenewKey(const CdmKeyResponse& key_response) {
|
|
if (!initialized_) {
|
|
LOGE("CDM session not initialized");
|
|
return CdmResponseType(NOT_INITIALIZED_ERROR);
|
|
}
|
|
CdmResponseType sts = license_parser_->HandleKeyUpdateResponse(
|
|
/* is renewal */ true,
|
|
/* is restore */ false, key_response);
|
|
|
|
// Record the timing on success.
|
|
UpdateRequestLatencyTiming(sts);
|
|
|
|
if (sts != KEY_ADDED)
|
|
return (sts == KEY_ERROR) ? CdmResponseType(RENEW_KEY_ERROR_1) : sts;
|
|
|
|
if (is_offline_) {
|
|
offline_key_renewal_response_ = key_response;
|
|
if (!StoreLicense(kLicenseStateActive, nullptr /* error_detail */))
|
|
return CdmResponseType(RENEW_KEY_ERROR_2);
|
|
}
|
|
return CdmResponseType(KEY_ADDED);
|
|
}
|
|
|
|
CdmResponseType CdmSession::GenerateReleaseRequest(CdmKeyRequest* key_request) {
|
|
if (!initialized_) {
|
|
LOGE("CDM session not initialized");
|
|
return CdmResponseType(NOT_INITIALIZED_ERROR);
|
|
}
|
|
RETURN_STATUS_IF_NULL(key_request);
|
|
is_release_ = true;
|
|
license_request_latency_.Clear();
|
|
CdmResponseType status = license_parser_->PrepareKeyUpdateRequest(
|
|
false, app_parameters_, usage_table_ == nullptr ? nullptr : this,
|
|
&key_request->message, &key_request->url);
|
|
|
|
key_request->type = kKeyRequestTypeRelease;
|
|
|
|
if (KEY_MESSAGE != status) return status;
|
|
|
|
if (has_provider_session_token() && SupportsUsageTable()) {
|
|
status = usage_table_->UpdateEntry(usage_entry_index_,
|
|
crypto_session_.get(), &usage_entry_);
|
|
|
|
if (status != NO_ERROR) {
|
|
LOGE("Update usage entry failed: status = %d", static_cast<int>(status));
|
|
return status;
|
|
}
|
|
}
|
|
|
|
if (is_offline_) { // Mark license as being released
|
|
if (!StoreLicense(kLicenseStateReleasing, nullptr))
|
|
return CdmResponseType(RELEASE_KEY_REQUEST_ERROR);
|
|
} else if (!usage_provider_session_token_.empty()) {
|
|
if (SupportsUsageTable()) {
|
|
if (!UpdateUsageInfo()) return CdmResponseType(RELEASE_USAGE_INFO_FAILED);
|
|
}
|
|
}
|
|
|
|
key_request_type_ = key_request->type;
|
|
license_request_latency_.Start(); // Start or restart timer.
|
|
|
|
return CdmResponseType(KEY_MESSAGE);
|
|
}
|
|
|
|
// ReleaseKey() - Accept release response and release license.
|
|
CdmResponseType CdmSession::ReleaseKey(const CdmKeyResponse& key_response) {
|
|
if (!initialized_) {
|
|
LOGE("CDM session not initialized");
|
|
return CdmResponseType(NOT_INITIALIZED_ERROR);
|
|
}
|
|
CdmResponseType sts = license_parser_->HandleKeyUpdateResponse(
|
|
/* is renewal */ false,
|
|
/* is restore */ false, key_response);
|
|
// Record the timing on success.
|
|
UpdateRequestLatencyTiming(sts);
|
|
|
|
if (sts != KEY_ADDED)
|
|
return (sts == KEY_ERROR) ? CdmResponseType(RELEASE_KEY_ERROR) : sts;
|
|
|
|
return RemoveLicense();
|
|
}
|
|
|
|
CdmResponseType CdmSession::DeleteUsageEntry(uint32_t usage_entry_index) {
|
|
if (!initialized_) {
|
|
LOGE("CDM session not initialized");
|
|
return CdmResponseType(NOT_INITIALIZED_ERROR);
|
|
}
|
|
if (!SupportsUsageTable()) {
|
|
LOGE("Cannot delete entry, usage table not supported");
|
|
return CdmResponseType(INCORRECT_USAGE_SUPPORT_TYPE_1);
|
|
}
|
|
|
|
// The usage entry cannot be deleted if it has a crypto session handling
|
|
// it, so close and reopen session.
|
|
UpdateUsageEntryInformation();
|
|
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_ = nullptr;
|
|
bool has_support = false;
|
|
if (crypto_session_->HasUsageTableSupport(&has_support) && has_support) {
|
|
usage_table_ = crypto_session_->GetUsageTable();
|
|
}
|
|
|
|
if (usage_table_ == nullptr) {
|
|
LOGE("Usage table header unavailable");
|
|
return CdmResponseType(INCORRECT_USAGE_SUPPORT_TYPE_1);
|
|
}
|
|
|
|
sts = usage_table_->InvalidateEntry(usage_entry_index, true,
|
|
file_handle_.get(), crypto_metrics_);
|
|
crypto_metrics_->usage_table_header_delete_entry_.Increment(sts);
|
|
return sts;
|
|
}
|
|
|
|
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_->GetLicenseOrRentalOrPlaybackDurationRemaining();
|
|
}
|
|
|
|
CdmSessionId CdmSession::GenerateSessionId() {
|
|
static int session_num = 1;
|
|
return SESSION_ID_PREFIX + std::to_string(++session_num);
|
|
}
|
|
|
|
bool CdmSession::GenerateKeySetId(bool atsc_mode_enabled,
|
|
CdmKeySetId* key_set_id) {
|
|
RETURN_FALSE_IF_NULL(key_set_id);
|
|
|
|
while (key_set_id->empty()) {
|
|
constexpr size_t random_size =
|
|
(kKeySetIdLength - sizeof(KEY_SET_ID_PREFIX)) / 2;
|
|
std::string random_data = wvutil::CdmRandom::RandomData(random_size);
|
|
if (random_data.size() != random_size) {
|
|
LOGE("Error generating random id.");
|
|
return false;
|
|
}
|
|
if (atsc_mode_enabled)
|
|
*key_set_id = ATSC_KEY_SET_ID_PREFIX + wvutil::b2a_hex(random_data);
|
|
else
|
|
*key_set_id = KEY_SET_ID_PREFIX + wvutil::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("Session type prohibits storage");
|
|
return CdmResponseType(STORAGE_PROHIBITED);
|
|
}
|
|
|
|
if (is_offline_) {
|
|
if (key_set_id_.empty()) {
|
|
LOGE("No key set ID");
|
|
return CdmResponseType(EMPTY_KEYSET_ID);
|
|
}
|
|
|
|
if (!license_parser_->is_offline()) {
|
|
LOGE("License policy prohibits storage");
|
|
return CdmResponseType(OFFLINE_LICENSE_PROHIBITED);
|
|
}
|
|
|
|
if (!StoreLicense(kLicenseStateActive, nullptr)) {
|
|
LOGE("Unable to store license");
|
|
return CdmResponseType(STORE_LICENSE_ERROR_1);
|
|
}
|
|
return CdmResponseType(NO_ERROR);
|
|
} // if (is_offline_)
|
|
|
|
std::string provider_session_token =
|
|
license_parser_->provider_session_token();
|
|
if (provider_session_token.empty()) {
|
|
LOGE("No provider session token and not offline");
|
|
return CdmResponseType(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_index_, drm_certificate_, wrapped_private_key_)) {
|
|
LOGE("Unable to store usage info");
|
|
// Usage info file is corrupt. Delete current usage entry and file.
|
|
if (SupportsUsageTable()) {
|
|
DeleteUsageEntry(usage_entry_index_);
|
|
} else {
|
|
LOGW("Cannot store, usage table not supported");
|
|
}
|
|
std::vector<std::string> provider_session_tokens;
|
|
file_handle_->DeleteAllUsageInfoForApp(
|
|
DeviceFiles::GetUsageInfoFileName(app_id), &provider_session_tokens);
|
|
|
|
return CdmResponseType(STORE_USAGE_INFO_ERROR);
|
|
}
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
|
|
bool CdmSession::StoreLicense(CdmOfflineLicenseState state, int* error_detail) {
|
|
DeviceFiles::ResponseType error_detail_alt = DeviceFiles::kNoError;
|
|
DeviceFiles::CdmLicenseData license_data{
|
|
key_set_id_,
|
|
state,
|
|
/* pssh_data = */ offline_init_data_,
|
|
/* license_request = */ key_request_,
|
|
/* license = */ key_response_,
|
|
/* license_renewal_request = */ offline_key_renewal_request_,
|
|
/* license_renewal = */ offline_key_renewal_response_,
|
|
offline_release_server_url_,
|
|
policy_engine_->GetPlaybackStartTime(),
|
|
policy_engine_->GetLastPlaybackTime(),
|
|
policy_engine_->GetGracePeriodEndTime(),
|
|
app_parameters_,
|
|
usage_entry_,
|
|
usage_entry_index_,
|
|
drm_certificate_,
|
|
wrapped_private_key_};
|
|
|
|
bool result = file_handle_->StoreLicense(license_data, &error_detail_alt);
|
|
if (error_detail != nullptr) {
|
|
*error_detail = static_cast<int>(error_detail_alt);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
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_, nullptr, crypto_session_.get()));
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
|
|
CdmResponseType CdmSession::RemoveLicense() {
|
|
if (is_offline_ || has_provider_session_token()) {
|
|
if (has_provider_session_token() && SupportsUsageTable()) {
|
|
DeleteUsageEntry(usage_entry_index_);
|
|
}
|
|
DeleteLicenseFile();
|
|
}
|
|
return CdmResponseType(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), key_set_id_);
|
|
}
|
|
}
|
|
|
|
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(kLicenseStateActive, nullptr);
|
|
}
|
|
}
|
|
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::UpdateUsageEntryInformation() {
|
|
if (!has_provider_session_token() || !SupportsUsageTable()) {
|
|
LOGE("Unexpected state: usage_support = %s, PST present = %s, ",
|
|
SupportsUsageTable() ? "true" : "false",
|
|
has_provider_session_token() ? "yes" : "no");
|
|
return CdmResponseType(INCORRECT_USAGE_SUPPORT_TYPE_2);
|
|
}
|
|
|
|
CdmResponseType sts(NO_ERROR);
|
|
// TODO(blueeyes): Add measurements to all UpdateEntry calls in a way that
|
|
// allos us to isolate this particular use case within
|
|
// UpdateUsageEntryInformation.
|
|
M_TIME(sts = usage_table_->UpdateEntry(usage_entry_index_,
|
|
crypto_session_.get(), &usage_entry_),
|
|
crypto_metrics_, usage_table_header_update_entry_, sts);
|
|
|
|
if (sts != NO_ERROR) return sts;
|
|
|
|
if (is_offline_)
|
|
StoreLicense(is_release_ ? kLicenseStateReleasing : kLicenseStateActive,
|
|
nullptr);
|
|
else if (!usage_provider_session_token_.empty())
|
|
UpdateUsageInfo();
|
|
|
|
return CdmResponseType(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) {
|
|
RETURN_STATUS_IF_NULL(out_buffer);
|
|
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) {
|
|
RETURN_STATUS_IF_NULL(out_buffer);
|
|
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) {
|
|
RETURN_STATUS_IF_NULL(signature);
|
|
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_index = usage_entry_index_;
|
|
|
|
return file_handle_->UpdateUsageInfo(
|
|
DeviceFiles::GetUsageInfoFileName(app_id), 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();
|
|
}
|
|
|
|
bool CdmSession::VerifyOfflineUsageEntry() {
|
|
// Check that the current license is the same as the expected
|
|
// entry in the usage table. It is possible that the license has
|
|
// been removed from the usage table but the license file remains.
|
|
if (usage_entry_index_ >= usage_table_->size()) {
|
|
LOGD("License usage entry does not exist: entry_index = %u, size = %zu",
|
|
usage_entry_index_, usage_table_->size());
|
|
return false;
|
|
}
|
|
const CdmUsageEntryInfo& entry_info =
|
|
usage_table_->entry_info_list().at(usage_entry_index_);
|
|
if (entry_info.storage_type != kStorageLicense ||
|
|
entry_info.key_set_id != key_set_id_) {
|
|
LOGD("License usage entry does not match");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
CdmResponseType CdmSession::LoadPrivateKey() {
|
|
std::string drm_certificate;
|
|
CryptoWrappedKey private_key;
|
|
uint32_t system_id;
|
|
|
|
if (file_handle_->RetrieveCertificate(atsc_mode_enabled_, &drm_certificate,
|
|
&private_key, nullptr, &system_id) !=
|
|
DeviceFiles::kCertificateValid) {
|
|
return CdmResponseType(NEED_PROVISIONING);
|
|
}
|
|
|
|
return LoadPrivateKey(drm_certificate, private_key);
|
|
}
|
|
|
|
CdmResponseType CdmSession::LoadPrivateOrLegacyKey(
|
|
const std::string& certificate, const CryptoWrappedKey& private_key) {
|
|
// Use provided key if valid
|
|
if (!certificate.empty() && private_key.IsValid())
|
|
return LoadPrivateKey(certificate, private_key);
|
|
|
|
// Otherwise use key from legacy certificate
|
|
std::string drm_certificate;
|
|
CryptoWrappedKey wrapped_private_key;
|
|
if (file_handle_->RetrieveLegacyCertificate(
|
|
&drm_certificate, &wrapped_private_key, nullptr, nullptr) !=
|
|
DeviceFiles::kCertificateValid)
|
|
return CdmResponseType(NEED_PROVISIONING);
|
|
|
|
return LoadPrivateKey(drm_certificate, wrapped_private_key);
|
|
}
|
|
|
|
CdmResponseType CdmSession::LoadPrivateKey(
|
|
const std::string& drm_certificate, const CryptoWrappedKey& private_key) {
|
|
CdmResponseType load_cert_sts;
|
|
M_TIME(
|
|
load_cert_sts = crypto_session_->LoadCertificatePrivateKey(private_key),
|
|
crypto_metrics_, crypto_session_load_certificate_private_key_,
|
|
load_cert_sts);
|
|
|
|
switch (load_cert_sts.Enum()) {
|
|
case NO_ERROR:
|
|
metrics_->drm_certificate_key_type_.Record(
|
|
DrmKeyTypeToMetricValue(private_key.type()));
|
|
|
|
drm_certificate_ = drm_certificate;
|
|
wrapped_private_key_ = std::move(private_key);
|
|
return CdmResponseType(NO_ERROR);
|
|
case SESSION_LOST_STATE_ERROR:
|
|
case SYSTEM_INVALIDATED_ERROR:
|
|
return load_cert_sts;
|
|
default:
|
|
return CdmResponseType(NEED_PROVISIONING);
|
|
}
|
|
}
|
|
|
|
// 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
|