From 774a078f1de5ccd58a25d12949248b191213686d Mon Sep 17 00:00:00 2001 From: Rahul Frias Date: Thu, 31 Oct 2013 12:07:28 -0700 Subject: [PATCH] Allow license renewals after expiry Licenses could be renewed uptil the point of expiry. After that point we expected that the session would have to be closed and a new one opened with a new license loaded. Clank requested that we support renewal of sessions past expiry. In addition, the error returned on decryption, if OEMCrypto determines that the KCB duration has expired, is NEED_KEY rather than KEY_ERROR. Merge of https://widevine-internal-review.googlesource.com/#/c/8240 from the widevine cdm repo. b/11390539 Change-Id: I023320f3f25514cd07b368701a92100429ce1c04 --- .../cdm/core/include/policy_engine.h | 5 +- libwvdrmengine/cdm/core/src/cdm_session.cpp | 12 +- .../cdm/core/src/crypto_session.cpp | 13 +- libwvdrmengine/cdm/core/src/policy_engine.cpp | 141 ++++++++---------- .../cdm/core/test/policy_engine_unittest.cpp | 80 +++++++++- 5 files changed, 165 insertions(+), 86 deletions(-) mode change 100755 => 100644 libwvdrmengine/cdm/core/src/crypto_session.cpp diff --git a/libwvdrmengine/cdm/core/include/policy_engine.h b/libwvdrmengine/cdm/core/include/policy_engine.h index 2dbeee70..650b5553 100644 --- a/libwvdrmengine/cdm/core/include/policy_engine.h +++ b/libwvdrmengine/cdm/core/include/policy_engine.h @@ -53,6 +53,9 @@ class PolicyEngine { return license_id_; } + bool IsLicenseDurationExpired(int64_t current_time); + bool IsPlaybackDurationExpired(int64_t current_time); + private: typedef enum { kLicenseStateInitial, @@ -65,8 +68,6 @@ class PolicyEngine { void Init(Clock* clock); - bool IsLicenseDurationExpired(int64_t current_time); - bool IsPlaybackDurationExpired(int64_t current_time); bool IsRenewalDelayExpired(int64_t current_time); bool IsRenewalRecoveryDurationExpired(int64_t current_time); bool IsRenewalRetryIntervalExpired(int64_t current_time); diff --git a/libwvdrmengine/cdm/core/src/cdm_session.cpp b/libwvdrmengine/cdm/core/src/cdm_session.cpp index 61b254ae..645d035b 100644 --- a/libwvdrmengine/cdm/core/src/cdm_session.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_session.cpp @@ -272,7 +272,17 @@ CdmResponseType CdmSession::Decrypt(const CdmDecryptionParameters& params) { if (crypto_session_.get() == NULL || !crypto_session_->IsOpen()) return UNKNOWN_ERROR; - return crypto_session_->Decrypt(params); + CdmResponseType status = crypto_session_->Decrypt(params); + // TODO(rfrias): Remove after support for OEMCrypto_ERROR_KEY_EXPIRED is in + if (UNKNOWN_ERROR == status) { + Clock clock; + int64_t current_time = clock.GetCurrentTime(); + if (policy_engine_.IsLicenseDurationExpired(current_time) || + policy_engine_.IsPlaybackDurationExpired(current_time)) { + return NEED_KEY; + } + } + return status; } // License renewal diff --git a/libwvdrmengine/cdm/core/src/crypto_session.cpp b/libwvdrmengine/cdm/core/src/crypto_session.cpp old mode 100755 new mode 100644 index fa23002f..b14d5f0e --- a/libwvdrmengine/cdm/core/src/crypto_session.cpp +++ b/libwvdrmengine/cdm/core/src/crypto_session.cpp @@ -588,10 +588,15 @@ CdmResponseType CryptoSession::Decrypt(const CdmDecryptionParameters& params) { params.is_encrypted, &(*params.iv).front(), params.block_offset, &buffer_descriptor, params.subsample_flags); - if (OEMCrypto_ERROR_INSUFFICIENT_RESOURCES == sts) { - return INSUFFICIENT_CRYPTO_RESOURCES; - } else if (OEMCrypto_SUCCESS != sts) { - return UNKNOWN_ERROR; + switch (sts) { + case OEMCrypto_SUCCESS: + break; + case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES: + return INSUFFICIENT_CRYPTO_RESOURCES; + case OEMCrypto_ERROR_KEY_EXPIRED: + return NEED_KEY; + default: + return UNKNOWN_ERROR; } return NO_ERROR; } diff --git a/libwvdrmengine/cdm/core/src/policy_engine.cpp b/libwvdrmengine/cdm/core/src/policy_engine.cpp index 5d4ef60d..8b1a9e40 100644 --- a/libwvdrmengine/cdm/core/src/policy_engine.cpp +++ b/libwvdrmengine/cdm/core/src/policy_engine.cpp @@ -106,95 +106,82 @@ void PolicyEngine::SetLicense( void PolicyEngine::UpdateLicense( const video_widevine_server::sdk::License& license) { - if (!license.has_policy() || kLicenseStateExpired == license_state_) + if (!license.has_policy()) return; policy_.MergeFrom(license.policy()); - switch (license_state_) { - case kLicenseStateExpired: - // Ignore policy updates. + if (!policy_.can_play()) { + license_state_ = kLicenseStateExpired; + return; + } + + // some basic license validation + if (license_state_ == kLicenseStateInitial) { + // license start time needs to be present in the initial response + if (!license.has_license_start_time()) return; + } + else { + // TODO(edwingwong, rfrias): Check back with Thomas and see if + // we need to enforce that all duration windows are absent if + // license_start_time is not present. This is a TBD. - case kLicenseStateInitial: - case kLicenseStateInitialPendingUsage: - case kLicenseStateCanPlay: - case kLicenseStateNeedRenewal: - case kLicenseStateWaitingLicenseUpdate: - if (!policy_.can_play()) { - license_state_ = kLicenseStateExpired; - return; - } + // if renewal, discard license if version has not been updated + if (license.id().version() > license_id_.version()) + license_id_.CopyFrom(license.id()); + else + return; + } - // some basic license validation - if (license_state_ == kLicenseStateInitial) { - // license start time needs to be present in the initial response - if (!license.has_license_start_time()) - return; - } - else { - // TODO(edwingwong, rfrias): Check back with Thomas and see if - // we need to enforce that all duration windows are absent if - // license_start_time is not present. This is a TBD. + // Update time information + int64_t current_time = clock_->GetCurrentTime(); + // TODO(edwingwong, rfrias): Check back with Thomas and see if + // we need to enforce that all duration windows are absent if + // license_start_time is not present. This is a TBD. + if (license.has_license_start_time()) + license_start_time_ = license.license_start_time(); + license_received_time_ = current_time; + next_renewal_time_ = current_time + + policy_.renewal_delay_seconds(); - // if renewal, discard license if version has not been updated - if (license.id().version() > license_id_.version()) - license_id_.CopyFrom(license.id()); - else - return; - } + // Calculate policy_max_duration_seconds_. policy_max_duration_seconds_ + // will be set to the minimum of the following policies : + // rental_duration_seconds and license_duration_seconds. + // The value is used to determine when the license expires. + policy_max_duration_seconds_ = 0; - // Update time information - int64_t current_time = clock_->GetCurrentTime(); - // TODO(edwingwong, rfrias): Check back with Thomas and see if - // we need to enforce that all duration windows are absent if - // license_start_time is not present. This is a TBD. - if (license.has_license_start_time()) - license_start_time_ = license.license_start_time(); - license_received_time_ = current_time; - next_renewal_time_ = current_time + - policy_.renewal_delay_seconds(); + if (policy_.has_rental_duration_seconds()) + policy_max_duration_seconds_ = policy_.rental_duration_seconds(); - // Calculate policy_max_duration_seconds_. policy_max_duration_seconds_ - // will be set to the minimum of the following policies : - // rental_duration_seconds and license_duration_seconds. - // The value is used to determine when the license expires. - policy_max_duration_seconds_ = 0; + if ((policy_.license_duration_seconds() > 0) && + ((policy_.license_duration_seconds() < + policy_max_duration_seconds_) || + policy_max_duration_seconds_ == 0)) { + policy_max_duration_seconds_ = policy_.license_duration_seconds(); + } - if (policy_.has_rental_duration_seconds()) - policy_max_duration_seconds_ = policy_.rental_duration_seconds(); + if (Properties::begin_license_usage_when_received()) + playback_start_time_ = current_time; - if ((policy_.license_duration_seconds() > 0) && - ((policy_.license_duration_seconds() < - policy_max_duration_seconds_) || - policy_max_duration_seconds_ == 0)) { - policy_max_duration_seconds_ = policy_.license_duration_seconds(); - } - - if (Properties::begin_license_usage_when_received()) - playback_start_time_ = current_time; - - // Update state - if (Properties::begin_license_usage_when_received()) { - if (policy_.renew_with_usage()) { - license_state_ = kLicenseStateNeedRenewal; - } - else { - license_state_ = kLicenseStateCanPlay; - can_decrypt_ = true; - } - } - else { - if (license_state_ == kLicenseStateInitial) { - license_state_ = kLicenseStateInitialPendingUsage; - } - else { - license_state_ = kLicenseStateCanPlay; - can_decrypt_ = true; - } - } - - break; + // Update state + if (Properties::begin_license_usage_when_received()) { + if (policy_.renew_with_usage()) { + license_state_ = kLicenseStateNeedRenewal; + } + else { + license_state_ = kLicenseStateCanPlay; + can_decrypt_ = true; + } + } + else { + if (license_state_ == kLicenseStateInitial) { + license_state_ = kLicenseStateInitialPendingUsage; + } + else { + license_state_ = kLicenseStateCanPlay; + can_decrypt_ = true; + } } } diff --git a/libwvdrmengine/cdm/core/test/policy_engine_unittest.cpp b/libwvdrmengine/cdm/core/test/policy_engine_unittest.cpp index 7d3afc48..e76e5cd1 100644 --- a/libwvdrmengine/cdm/core/test/policy_engine_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/policy_engine_unittest.cpp @@ -438,7 +438,7 @@ TEST_F(PolicyEngineTest, PlaybackFailed_RenewFailedVersionNotUpdated) { EXPECT_FALSE(policy_engine_->can_decrypt()); } -TEST_F(PolicyEngineTest, PlaybackOk_RepeatedRenewFailures) { +TEST_F(PolicyEngineTest, PlaybackFailed_RepeatedRenewFailures) { EXPECT_CALL(*mock_clock_, GetCurrentTime()) .WillOnce(Return(license_start_time_ + 1)) .WillOnce(Return(license_start_time_ + license_duration_ - @@ -496,7 +496,83 @@ TEST_F(PolicyEngineTest, PlaybackOk_RepeatedRenewFailures) { EXPECT_FALSE(policy_engine_->can_decrypt()); } -TEST_F(PolicyEngineTest, PlaybackOk_RenewedSuccessAfterExpiry) { +TEST_F(PolicyEngineTest, PlaybackOk_RenewSuccessAfterExpiry) { + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(license_start_time_ + 1)) + .WillOnce(Return(license_start_time_ + license_duration_ - + playback_duration_ + 1)) + .WillOnce(Return(license_start_time_ + license_renewal_delay_ - 10)) + .WillOnce(Return(license_start_time_ + license_renewal_delay_ + 10)) + .WillOnce(Return(license_start_time_ + license_renewal_delay_ + 20)) + .WillOnce(Return(license_start_time_ + license_renewal_delay_ + 40)) + .WillOnce(Return(license_start_time_ + license_renewal_delay_ + 50)) + .WillOnce(Return(license_start_time_ + license_renewal_delay_ + 70)) + .WillOnce(Return(license_start_time_ + license_renewal_delay_ + 80)) + .WillOnce(Return(license_start_time_ + license_duration_ + 10)) + .WillOnce(Return(license_start_time_ + license_duration_ + 30)) + .WillOnce(Return(license_start_time_ + license_duration_ + 40)); + + policy_engine_->SetLicense(license_); + + policy_engine_->BeginDecryption(); + EXPECT_TRUE(policy_engine_->can_decrypt()); + + bool event_occurred; + CdmEventType event; + policy_engine_->OnTimerEvent(event_occurred, event); + EXPECT_FALSE(event_occurred); + + policy_engine_->OnTimerEvent(event_occurred, event); + EXPECT_TRUE(event_occurred); + EXPECT_EQ(LICENSE_RENEWAL_NEEDED_EVENT, event); + + EXPECT_TRUE(policy_engine_->can_decrypt()); + + policy_engine_->OnTimerEvent(event_occurred, event); + EXPECT_FALSE(event_occurred); + + policy_engine_->OnTimerEvent(event_occurred, event); + EXPECT_TRUE(event_occurred); + EXPECT_EQ(LICENSE_RENEWAL_NEEDED_EVENT, event); + + EXPECT_TRUE(policy_engine_->can_decrypt()); + + policy_engine_->OnTimerEvent(event_occurred, event); + EXPECT_FALSE(event_occurred); + + policy_engine_->OnTimerEvent(event_occurred, event); + EXPECT_TRUE(event_occurred); + EXPECT_EQ(LICENSE_RENEWAL_NEEDED_EVENT, event); + + EXPECT_TRUE(policy_engine_->can_decrypt()); + + policy_engine_->OnTimerEvent(event_occurred, event); + EXPECT_FALSE(event_occurred); + + policy_engine_->OnTimerEvent(event_occurred, event); + EXPECT_TRUE(event_occurred); + EXPECT_EQ(LICENSE_EXPIRED_EVENT, event); + + EXPECT_FALSE(policy_engine_->can_decrypt()); + + license_.set_license_start_time(license_start_time_ + + license_duration_ + 20); + LicenseIdentification* id = license_.mutable_id(); + id->set_version(2); + License_Policy* policy = license_.mutable_policy(); + policy = license_.mutable_policy(); + policy->set_playback_duration_seconds(playback_duration_ + 100); + policy->set_license_duration_seconds(license_duration_ + 100); + + policy_engine_->UpdateLicense(license_); + + policy_engine_->OnTimerEvent(event_occurred, event); + EXPECT_FALSE(event_occurred); + + EXPECT_TRUE(policy_engine_->can_decrypt()); +} + +TEST_F(PolicyEngineTest, PlaybackOk_RenewSuccessAfterFailures) { EXPECT_CALL(*mock_clock_, GetCurrentTime()) .WillOnce(Return(license_start_time_ + 1)) .WillOnce(Return(license_start_time_ + license_duration_ -