Files
android/libwvdrmengine/cdm/core/src/cdm_session.cpp
John W. Bruce bdd7b9d89f Allow CDM to Build With Recent GCC
(This is a merge of http://go/wvgerrit/100051. However, only one part of
that change affects the Android code, so I have filtered this
description.)

By default, the CDM builds with Clang on Android and on developers' dev
boxes. The buildbot builds most of the code with an old version of GCC.
However, recent versions of GCC were refusing to build our code for a
variety of reasons. This patch fixes the codebase up so that the
version of GCC 9 included on gLinux workstations can compile the CDM.

The only change that affects Android is that a variable was being set
but never read in one place.

Test: Android Unit Tests
Bug: 145245240
Bug: 152449437
Change-Id: Iaeb0531652bb8e7bd69f850fc6b4bba1efa3271b
2020-05-15 12:58:24 -07:00

1178 lines
40 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 <algorithm>
#include <iterator>
#include <string>
#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"
// 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 PARAMETER_NULL; \
}
#define RETURN_FALSE_IF_NULL(PARAM) \
if ((PARAM) == nullptr) { \
LOGE("Output parameter |" STRINGIFY(PARAM) "| not provided"); \
return false; \
}
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 = error_code;
}
}
} // namespace
namespace wvcdm {
CdmSession::CdmSession(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_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_(nullptr),
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_ != nullptr &&
!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);
}
CdmResponseType CdmSession::Init(CdmClientPropertySet* cdm_client_property_set,
const CdmSessionId* forced_session_id,
WvCdmEventListener* event_listener) {
if (initialized_) {
LOGE("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("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, nullptr)) {
return NEED_PROVISIONING;
}
CdmResponseType load_cert_sts;
M_TIME(
load_cert_sts = crypto_session_->LoadCertificatePrivateKey(wrapped_key),
crypto_metrics_, crypto_session_load_certificate_private_key_,
load_cert_sts);
switch (load_cert_sts) {
case NO_ERROR:
break;
case SESSION_LOST_STATE_ERROR:
case SYSTEM_INVALIDATED_ERROR:
return load_cert_sts;
default:
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("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,
int* error_detail) {
if (!initialized_) {
LOGE("CDM session not initialized");
return NOT_INITIALIZED_ERROR;
}
if (!key_set_id_.empty()) {
file_handle_->UnreserveLicenseId(key_set_id_);
}
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 = %d, key_set_id = %s",
static_cast<int>(sub_error_code), key_set_id.c_str());
SetErrorDetail(error_detail, sub_error_code);
return sub_error_code == DeviceFiles::kFileNotFound ? KEYSET_ID_NOT_FOUND_4
: 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_number_ = license_data.usage_entry_number;
// 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 == DeviceFiles::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 == DeviceFiles::kLicenseStateActive)) {
LOGE("Invalid offline license state: state = %d, license_type = %d",
static_cast<int>(license_data.state), static_cast<int>(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_ == nullptr) {
provider_session_token.clear();
} else if (!VerifyOfflineUsageEntry()) {
LOGE("License usage entry is invalid, cannot restore");
return LICENSE_USAGE_ENTRY_MISSING;
} else {
CdmResponseType sts = usage_table_header_->LoadEntry(
crypto_session_.get(), usage_entry_, usage_entry_number_);
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;
}
}
}
CdmResponseType result;
if (license_type == kLicenseTypeRelease) {
result =
license_parser_->RestoreLicenseForRelease(key_request_, key_response_);
if (result != NO_ERROR) {
SetErrorDetail(error_detail, result);
return RELEASE_LICENSE_ERROR_1;
}
} else {
result = license_parser_->RestoreOfflineLicense(
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);
return RESTORE_OFFLINE_LICENSE_ERROR_2;
}
}
if (usage_support_type_ == kUsageEntrySupport &&
!provider_session_token.empty() && usage_table_header_ != nullptr) {
CdmResponseType sts = usage_table_header_->UpdateEntry(
usage_entry_number_, 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;
return KEY_ADDED;
}
CdmResponseType CdmSession::RestoreUsageSession(
const DeviceFiles::CdmUsageData& usage_data, int* error_detail) {
if (!initialized_) {
LOGE("CDM session 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_ != nullptr) {
sts = usage_table_header_->LoadEntry(crypto_session_.get(), usage_entry_,
usage_entry_number_);
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(key_request_, key_response_);
if (sts != NO_ERROR) {
SetErrorDetail(error_detail, sts);
return RELEASE_LICENSE_ERROR_2;
}
if (usage_support_type_ == kUsageEntrySupport &&
usage_table_header_ != nullptr) {
sts = usage_table_header_->UpdateEntry(
usage_entry_number_, 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 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 NOT_INITIALIZED_ERROR;
}
RETURN_STATUS_IF_NULL(key_request);
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(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 INVALID_LICENSE_TYPE;
}
if (is_release_) {
return GenerateReleaseRequest(key_request);
} else 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);
} else {
return GenerateRenewalRequest(key_request);
}
} else {
key_request->type = kKeyRequestTypeInitial;
if (!init_data.is_supported()) {
LOGW("Unsupported init data type: %s", init_data.type().c_str());
return UNSUPPORTED_INIT_DATA;
}
if (init_data.IsEmpty() && !license_parser_->HasInitData()) {
LOGW("Init data absent");
return INIT_DATA_NOT_FOUND;
}
if (is_offline_ && key_set_id_.empty()) {
LOGE("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("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_ != nullptr) {
if (license_parser_->ExtractProviderSessionToken(
key_response, &provider_session_token) &&
!provider_session_token.empty()) {
std::string app_id;
GetApplicationId(&app_id);
sts = usage_table_header_->AddEntry(
crypto_session_.get(), is_offline_, key_set_id_,
DeviceFiles::GetUsageInfoFileName(app_id), key_response,
&usage_entry_number_);
crypto_metrics_->usage_table_header_add_entry_.Increment(sts);
if (sts != NO_ERROR) return sts;
}
}
sts = license_parser_->HandleKeyResponse(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_sdk_version_.Record(
version_info.license_service_version());
// Update or invalidate entry if usage table header+entries are supported
if (usage_support_type_ == kUsageEntrySupport &&
!provider_session_token.empty() && usage_table_header_ != nullptr) {
if (sts != KEY_ADDED) {
const CdmResponseType invalidate_sts =
usage_table_header_->InvalidateEntry(
usage_entry_number_, 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) ? ADD_KEY_ERROR : sts;
license_received_ = true;
key_response_ = key_response;
LOGV("Key added: provider_session_token = %s (size = %zu)",
license_parser_->provider_session_token().c_str(),
license_parser_->provider_session_token().size());
if (is_offline_ || has_provider_session_token()) {
if (has_provider_session_token() &&
usage_support_type_ == kUsageEntrySupport &&
usage_table_header_ != nullptr) {
usage_table_header_->UpdateEntry(usage_entry_number_,
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("CDM session not initialized");
return 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 INVALID_QUERY_KEY;
}
return 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 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 NO_ERROR;
}
// Decrypt() - Accept encrypted buffer and return decrypted data.
CdmResponseType CdmSession::Decrypt(const CdmDecryptionParametersV16& params) {
if (!initialized_) {
return 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 DECRYPT_NOT_READY;
if (!policy_engine_->IsSufficientOutputProtection(params.key_id))
return INSUFFICIENT_OUTPUT_PROTECTION;
return NEED_KEY;
}
if (!policy_engine_->CanUseKeyForSecurityLevel(params.key_id)) {
return KEY_PROHIBITED_FOR_SECURITY_LEVEL;
}
}
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();
}
} else {
Clock clock;
int64_t current_time = clock.GetCurrentTime();
if (policy_engine_->HasLicenseOrRentalOrPlaybackDurationExpired(
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("CDM session not initialized");
return 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 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 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,
nullptr /* error_detail */))
return RENEW_KEY_ERROR_2;
}
return KEY_ADDED;
}
CdmResponseType CdmSession::GenerateReleaseRequest(CdmKeyRequest* key_request) {
if (!initialized_) {
LOGE("CDM session not initialized");
return 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_header_ == nullptr ? nullptr : 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(
usage_entry_number_, 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(DeviceFiles::kLicenseStateReleasing, nullptr))
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("CDM session 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("CDM session not initialized");
return NOT_INITIALIZED_ERROR;
}
if (usage_support_type_ != kUsageEntrySupport) {
LOGE("Unexpected usage support type: %d",
static_cast<int>(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.
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_header_ = nullptr;
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_ == nullptr) {
LOGE("Usage table header unavailable");
return INCORRECT_USAGE_SUPPORT_TYPE_1;
}
sts = usage_table_header_->InvalidateEntry(
usage_entry_number, 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 + IntToString(++session_num);
}
bool CdmSession::GenerateKeySetId(CdmKeySetId* key_set_id) {
RETURN_FALSE_IF_NULL(key_set_id);
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]) !=
NO_ERROR) {
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("Session type prohibits storage");
return STORAGE_PROHIBITED;
}
if (is_offline_) {
if (key_set_id_.empty()) {
LOGE("No key set ID");
return EMPTY_KEYSET_ID;
}
if (!license_parser_->is_offline()) {
LOGE("License policy prohibits storage");
return OFFLINE_LICENSE_PROHIBITED;
}
if (!StoreLicense(DeviceFiles::kLicenseStateActive, nullptr)) {
LOGE("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("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("Unable to store usage info");
// Usage info file is corrupt. Delete current usage entry and file.
if (usage_support_type_ == kUsageEntrySupport) {
DeleteUsageEntry(usage_entry_number_);
} else {
LOGW("Unexpected usage support type: %d",
static_cast<int>(usage_support_type_));
}
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,
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_number_};
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 NO_ERROR;
}
CdmResponseType CdmSession::RemoveLicense() {
if (is_offline_ || has_provider_session_token()) {
if (usage_support_type_ == kUsageEntrySupport &&
has_provider_session_token()) {
DeleteUsageEntry(usage_entry_number_);
}
DeleteLicenseFile();
}
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, 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 (usage_support_type_ != kUsageEntrySupport ||
!has_provider_session_token() || usage_table_header_ == nullptr) {
LOGE(
"Unexpected state: usage support type = %d, PST present = %s, "
"usage table header available = %s",
static_cast<int>(usage_support_type_),
has_provider_session_token() ? "yes" : "no",
usage_table_header_ == nullptr ? "no" : "yes");
return 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_header_->UpdateEntry(
usage_entry_number_, 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_ ? DeviceFiles::kLicenseStateReleasing
: DeviceFiles::kLicenseStateActive,
nullptr);
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) {
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_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();
}
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_number_ >= usage_table_header_->size()) {
LOGD("License usage entry does not exist: entry_number = %u, size = %zu",
usage_entry_number_, usage_table_header_->size());
return false;
}
const CdmUsageEntryInfo& usage_entry_info =
usage_table_header_->usage_entry_info().at(usage_entry_number_);
if (usage_entry_info.storage_type != kStorageLicense ||
usage_entry_info.key_set_id != key_set_id_) {
LOGD("License usage entry does not match");
return false;
}
return true;
}
// 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