Files
android/libwvdrmengine/cdm/core/src/policy_engine.cpp
Alex Dale b1fad7f4cb Reset crypto session pointers on RemoveKeys.
[ Merge of http://go/wvgerrit/189590 ]
[ Cherry-pick of http://ag/26541307 ]

The CDM session shares its CryptoSession instance with a few additional
member objects (CdmLicense and PolicyEngine).  When the CDM session's
crypto session is reset, it must also reset the CdmLicense and
PolicyEngine otherwise, a potential stale pointer reference may occur.

Test: request_license_test on Oriole
Test: run_x86_64_tests
Bug: 311239278
Change-Id: Ie175513ae652dcd96e12e5e1def574a8a56d5863
2024-03-14 00:19:25 -07:00

447 lines
14 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 "policy_engine.h"
#include <string>
#include "clock.h"
#include "log.h"
#include "policy_timers_v16.h"
#include "policy_timers_v18.h"
#include "properties.h"
#include "string_conversions.h"
#include "wv_cdm_constants.h"
#include "wv_cdm_event_listener.h"
using video_widevine::License;
namespace {
const int kCdmPolicyTimerDurationSeconds = 1;
const int kClockSkewDelta = 5; // seconds
const int64_t kLicenseStateUpdateDelay = 20; // seconds
const uint32_t kMinOemCryptoApiVersionSupportsRenewalDelayBase = 18;
} // namespace
namespace wvcdm {
PolicyEngine::PolicyEngine(CdmSessionId session_id,
WvCdmEventListener* event_listener,
CryptoSession* crypto_session)
: session_id_(std::move(session_id)),
event_listener_(event_listener),
license_keys_(new LicenseKeys(crypto_session->GetSecurityLevel())),
clock_(new wvutil::Clock()) {
uint32_t version = 0;
if (crypto_session->GetApiVersion(&version)) {
if (version >= kMinOemCryptoApiVersionSupportsRenewalDelayBase) {
policy_timers_.reset(new PolicyTimersV18());
}
} else {
LOGW("Failed to get API version: session_id = %s", IdToString(session_id));
}
if (!policy_timers_) {
// Use V16 policy timers if getting version failed.
policy_timers_.reset(new PolicyTimersV16());
}
InitDevice(crypto_session);
}
PolicyEngine::~PolicyEngine() {}
bool PolicyEngine::CanDecryptContent(const KeyId& key_id) {
if (license_keys_->IsContentKey(key_id)) {
return license_keys_->CanDecryptContent(key_id);
}
LOGE("Provided content key is not in license: key_id = %s",
wvutil::b2a_hex(key_id).c_str());
return false;
}
CdmKeyStatus PolicyEngine::GetKeyStatus(const KeyId& key_id) {
return license_keys_->GetKeyStatus(key_id);
}
void PolicyEngine::InitDevice(CryptoSession* crypto_session) {
current_resolution_ = HDCP_UNSPECIFIED_VIDEO_RESOLUTION;
next_device_check_ = 0;
crypto_session_ = crypto_session;
}
void PolicyEngine::SetDeviceResolution(uint32_t width, uint32_t height) {
current_resolution_ = width * height;
CheckDeviceHdcpStatus();
}
void PolicyEngine::CheckDeviceHdcpStatusOnTimer(int64_t current_time) {
if (current_time >= next_device_check_) {
CheckDeviceHdcpStatus();
next_device_check_ = current_time + HDCP_DEVICE_CHECK_INTERVAL;
}
}
void PolicyEngine::CheckDeviceHdcpStatus() {
if (!license_keys_->Empty()) {
CryptoSession::HdcpCapability current_hdcp_level = HDCP_NONE;
CryptoSession::HdcpCapability ignored = HDCP_NONE;
const CdmResponseType status =
crypto_session_->GetHdcpCapabilities(&current_hdcp_level, &ignored);
if (status != NO_ERROR) {
current_hdcp_level = HDCP_NONE;
}
license_keys_->ApplyConstraints(current_resolution_, current_hdcp_level);
}
}
void PolicyEngine::OnTimerEvent() {
last_recorded_current_time_ += kCdmPolicyTimerDurationSeconds;
const int64_t current_time = GetCurrentTime();
// This conditional should not succeed but being cautious in case license
// state was not updated after the license was processed. Licenses and
// renewals from the license service should update state on
// |Set/UpdateLicense|. Offline licenses, when restored, should update license
// state when |RestorePlaybackTimes| is called.
if (license_state_update_deadline_ != 0 &&
license_state_update_deadline_ < current_time) {
LOGW("License state was not updated after a license was loaded/renewed");
UpdateLicenseState(current_time);
}
// If we have passed the grace period, the expiration will update.
if (license_state_ != kLicenseStateInitial &&
policy_timers_->HasPassedGracePeriod(current_time)) {
NotifyExpirationUpdate(current_time);
}
// License expiration trumps all.
if (policy_timers_->HasLicenseOrRentalOrPlaybackDurationExpired(
current_time) &&
license_state_ != kLicenseStateExpired) {
license_state_ = kLicenseStateExpired;
NotifyKeysChange(kKeyStatusExpired);
return;
}
// Check device conditions that affect playability (HDCP, resolution)
CheckDeviceHdcpStatusOnTimer(current_time);
bool renewal_needed = false;
// Test to determine if renewal should be attempted.
switch (license_state_) {
case kLicenseStateCanPlay: {
if (policy_timers_->HasRenewalDelayExpired(current_time) &&
policy_timers_->get_policy().can_renew()) {
renewal_needed = true;
}
// HDCP may change, so force a check.
NotifyKeysChange(kKeyStatusUsable);
break;
}
case kLicenseStateNeedRenewal: {
if (policy_timers_->get_policy().can_renew()) {
renewal_needed = true;
}
break;
}
case kLicenseStateWaitingLicenseUpdate: {
if (policy_timers_->HasRenewalRetryIntervalExpired(current_time) &&
policy_timers_->get_policy().can_renew()) {
renewal_needed = true;
}
break;
}
case kLicenseStatePending: {
if (!policy_timers_->IsLicenseForFuture(current_time)) {
license_state_ = kLicenseStateCanPlay;
NotifyKeysChange(kKeyStatusUsable);
}
break;
}
case kLicenseStateInitial:
case kLicenseStateExpired: {
break;
}
default: {
license_state_ = kLicenseStateExpired;
NotifyKeysChange(kKeyStatusInternalError);
break;
}
}
if (renewal_needed) {
UpdateRenewalRequest(current_time);
if (event_listener_) event_listener_->OnSessionRenewalNeeded(session_id_);
}
}
void PolicyEngine::SetLicense(const License& license,
bool defer_license_state_update) {
license_id_.CopyFrom(license.id());
license_keys_->SetFromLicense(license);
policy_timers_->SetLicense(license);
UpdateLicense(license, defer_license_state_update);
}
void PolicyEngine::SetEntitledLicenseKeys(
const std::vector<WidevinePsshData_EntitledKey>& entitled_keys) {
license_keys_->SetEntitledKeys(entitled_keys);
}
void PolicyEngine::SetLicenseForRelease(const License& license) {
license_id_.CopyFrom(license.id());
// Expire any old keys.
NotifyKeysChange(kKeyStatusExpired);
policy_timers_->SetLicense(license);
UpdateLicense(license, false);
}
void PolicyEngine::UpdateLicense(const License& license,
bool defer_license_state_update) {
if (!license.has_policy()) return;
if (kLicenseStateExpired == license_state_) {
LOGD("Updating an expired license");
}
// some basic license validation
// license start time needs to be specified in the initial response
if (!license.has_license_start_time()) return;
// if renewal, discard license if version has not been updated
if (license_state_ != kLicenseStateInitial) {
if (license.id().version() > license_id_.version()) {
license_id_.CopyFrom(license.id());
} else {
return;
}
}
const int64_t current_time = GetCurrentTime();
policy_timers_->UpdateLicense(current_time, license);
if (defer_license_state_update) {
license_state_update_deadline_ = current_time + kLicenseStateUpdateDelay;
} else {
UpdateLicenseState(current_time);
}
}
void PolicyEngine::UpdateLicenseState(int64_t current_time) {
// Update time information
license_state_update_deadline_ = 0;
if (!policy_timers_->get_policy().can_play() ||
policy_timers_->HasLicenseOrRentalOrPlaybackDurationExpired(
current_time)) {
license_state_ = kLicenseStateExpired;
NotifyKeysChange(kKeyStatusExpired);
return;
}
// Update state
if (!policy_timers_->IsLicenseForFuture(current_time)) {
license_state_ = kLicenseStateCanPlay;
NotifyKeysChange(kKeyStatusUsable);
} else {
license_state_ = kLicenseStatePending;
NotifyKeysChange(kKeyStatusUsableInFuture);
}
NotifyExpirationUpdate(current_time);
}
bool PolicyEngine::BeginDecryption() {
const int64_t current_time = GetCurrentTime();
if (!policy_timers_->HasPlaybackStarted(current_time)) {
switch (license_state_) {
case kLicenseStateCanPlay:
case kLicenseStateNeedRenewal:
case kLicenseStateWaitingLicenseUpdate:
policy_timers_->BeginDecryption(current_time);
if (policy_timers_->get_policy().renew_with_usage() &&
policy_timers_->get_policy().can_renew()) {
license_state_ = kLicenseStateNeedRenewal;
}
NotifyExpirationUpdate(current_time);
return true;
case kLicenseStateInitial:
case kLicenseStatePending:
case kLicenseStateExpired:
default:
return false;
}
} else {
return true;
}
}
void PolicyEngine::DecryptionEvent() {
policy_timers_->DecryptionEvent(GetCurrentTime());
}
void PolicyEngine::NotifyResolution(uint32_t width, uint32_t height) {
SetDeviceResolution(width, height);
}
void PolicyEngine::NotifySessionExpiration() {
license_state_ = kLicenseStateExpired;
NotifyKeysChange(kKeyStatusExpired);
}
CdmResponseType PolicyEngine::Query(CdmQueryMap* query_response) {
const int64_t current_time = GetCurrentTime();
if (license_state_ == kLicenseStateInitial) {
query_response->clear();
return CdmResponseType(NO_ERROR);
}
(*query_response)[QUERY_KEY_LICENSE_TYPE] =
license_id_.type() == video_widevine::STREAMING ? QUERY_VALUE_STREAMING
: QUERY_VALUE_OFFLINE;
(*query_response)[QUERY_KEY_PLAY_ALLOWED] =
policy_timers_->get_policy().can_play() ? QUERY_VALUE_TRUE
: QUERY_VALUE_FALSE;
(*query_response)[QUERY_KEY_PERSIST_ALLOWED] =
policy_timers_->get_policy().can_persist() ? QUERY_VALUE_TRUE
: QUERY_VALUE_FALSE;
(*query_response)[QUERY_KEY_RENEW_ALLOWED] =
policy_timers_->get_policy().can_renew() ? QUERY_VALUE_TRUE
: QUERY_VALUE_FALSE;
(*query_response)[QUERY_KEY_LICENSE_DURATION_REMAINING] = std::to_string(
policy_timers_->GetLicenseOrRentalDurationRemaining(current_time));
(*query_response)[QUERY_KEY_RENTAL_DURATION_REMAINING] = std::to_string(
policy_timers_->GetLicenseOrRentalDurationRemaining(current_time));
(*query_response)[QUERY_KEY_PLAYBACK_DURATION_REMAINING] = std::to_string(
policy_timers_->GetPlaybackDurationRemaining(current_time));
(*query_response)[QUERY_KEY_RENEWAL_SERVER_URL] =
policy_timers_->get_policy().renewal_server_url();
return CdmResponseType(NO_ERROR);
}
CdmResponseType PolicyEngine::QueryKeyAllowedUsage(
const KeyId& key_id, CdmKeyAllowedUsage* key_usage) {
if (key_usage == nullptr) {
LOGE("Output parameter |key_usage| not provided");
return CdmResponseType(PARAMETER_NULL);
}
if (license_keys_->GetAllowedUsage(key_id, key_usage)) {
return CdmResponseType(NO_ERROR);
}
return CdmResponseType(KEY_NOT_FOUND_1);
}
bool PolicyEngine::CanUseKeyForSecurityLevel(const KeyId& key_id) {
return license_keys_->MeetsSecurityLevelConstraints(key_id);
}
bool PolicyEngine::GetSecondsSinceStarted(int64_t* seconds_since_started) {
return policy_timers_->GetSecondsSinceStarted(GetCurrentTime(),
seconds_since_started);
}
bool PolicyEngine::GetSecondsSinceLastPlayed(
int64_t* seconds_since_last_played) {
return policy_timers_->GetSecondsSinceLastPlayed(GetCurrentTime(),
seconds_since_last_played);
}
int64_t PolicyEngine::GetLicenseOrRentalOrPlaybackDurationRemaining() {
return policy_timers_->GetLicenseOrRentalOrPlaybackDurationRemaining(
GetCurrentTime());
}
bool PolicyEngine::CanRenew() const {
return policy_timers_->get_policy().can_renew();
}
int64_t PolicyEngine::GetPlaybackStartTime() {
return policy_timers_->GetPlaybackStartTime();
}
int64_t PolicyEngine::GetLastPlaybackTime() {
return policy_timers_->GetLastPlaybackTime();
}
int64_t PolicyEngine::GetGracePeriodEndTime() {
return policy_timers_->GetGracePeriodEndTime();
}
void PolicyEngine::RestorePlaybackTimes(int64_t playback_start_time,
int64_t last_playback_time,
int64_t grace_period_end_time) {
const int64_t current_time = GetCurrentTime();
policy_timers_->RestorePlaybackTimes(current_time, playback_start_time,
last_playback_time,
grace_period_end_time);
UpdateLicenseState(current_time);
}
void PolicyEngine::UpdateRenewalRequest(int64_t current_time) {
license_state_ = kLicenseStateWaitingLicenseUpdate;
policy_timers_->UpdateRenewalRequest(current_time);
}
bool PolicyEngine::HasLicenseOrRentalOrPlaybackDurationExpired(
int64_t current_time) {
return policy_timers_->HasLicenseOrRentalOrPlaybackDurationExpired(
current_time);
}
// Apply a key status to the current keys.
// If this represents a new key status, perform a notification callback.
// NOTE: if the new status is kKeyStatusUsable, the HDCP check may result in an
// override to kKeyStatusOutputNotAllowed.
void PolicyEngine::NotifyKeysChange(CdmKeyStatus new_status) {
bool keys_changed;
bool has_new_usable_key = false;
if (new_status == kKeyStatusUsable) {
CheckDeviceHdcpStatus();
}
keys_changed =
license_keys_->ApplyStatusChange(new_status, &has_new_usable_key);
if (event_listener_ && keys_changed) {
CdmKeyStatusMap content_keys;
license_keys_->ExtractKeyStatuses(&content_keys);
event_listener_->OnSessionKeysChange(session_id_, content_keys,
has_new_usable_key);
}
}
void PolicyEngine::NotifyExpirationUpdate(int64_t current_time) {
int64_t expiry_time;
if (policy_timers_->UpdateExpirationTime(current_time, &expiry_time)) {
if (event_listener_) {
event_listener_->OnExpirationUpdate(session_id_, expiry_time);
}
}
}
int64_t PolicyEngine::GetCurrentTime() {
int64_t current_time = clock_->GetCurrentTime();
if (current_time + kClockSkewDelta < last_recorded_current_time_) {
current_time = last_recorded_current_time_;
} else {
last_recorded_current_time_ = current_time;
}
return current_time;
}
void PolicyEngine::set_clock(wvutil::Clock* clock) { clock_.reset(clock); }
void PolicyEngine::SetSecurityLevelForTest(CdmSecurityLevel security_level) {
license_keys_->SetSecurityLevelForTest(security_level);
}
} // namespace wvcdm