From 87e84d5498ee96171d17ee3fcf1ae159ec0d128d Mon Sep 17 00:00:00 2001 From: Rahul Frias Date: Fri, 23 Apr 2021 03:57:10 -0700 Subject: [PATCH 1/2] Use Usage information from OEMCrypto on clock rollback [ Merge of http://go/wvgerrit/123103 ] This corrects setting of first and last playback times stored by the CDM on rollback. Earlier usage information from the usage entry in OEMCrypto would be ignored on rollback even when available. Information stored along with the license in persistent storage would be used instead. A new test with longer duration expiry has been added as well as some additional verification. Bug: 186199213 Test: WV unit/integration test Change-Id: I601f9584a8a0c5137ce68546f8ec833bf2e70cc5 --- libwvdrmengine/cdm/core/src/license.cpp | 8 +-- .../cdm/test/request_license_test.cpp | 69 ++++++++++++++++++- 2 files changed, 72 insertions(+), 5 deletions(-) diff --git a/libwvdrmengine/cdm/core/src/license.cpp b/libwvdrmengine/cdm/core/src/license.cpp index 9194f5be..f85cbfe6 100644 --- a/libwvdrmengine/cdm/core/src/license.cpp +++ b/libwvdrmengine/cdm/core/src/license.cpp @@ -864,13 +864,13 @@ CdmResponseType CdmLicense::RestoreOfflineLicense( break; case CryptoSession::kUsageDurationsValid: { int64_t current_time = clock_->GetCurrentTime(); - if (current_time - seconds_since_started > 0) - playback_start_time = current_time - seconds_since_started; - if (current_time - last_playback_time > 0) - last_playback_time = current_time - seconds_since_last_played; + playback_start_time = current_time - seconds_since_started; + last_playback_time = current_time - seconds_since_last_played; break; } default: + // Use playback_start_time and last_playback_time from + // persistently stored license data break; } } diff --git a/libwvdrmengine/cdm/test/request_license_test.cpp b/libwvdrmengine/cdm/test/request_license_test.cpp index e5dcf686..4c392c87 100644 --- a/libwvdrmengine/cdm/test/request_license_test.cpp +++ b/libwvdrmengine/cdm/test/request_license_test.cpp @@ -6593,6 +6593,69 @@ TEST_F(WvCdmRequestLicenseRollbackTest, Offline_RollbackBeforeRestoreKey) { ASSERT_EQ(NO_ERROR, decryptor_->CloseSession(session_id_)); } +// The difference between this test and Offline_RollbackBeforeRestoreKey is +// that this test has a 15 second expiration window. +// Offline_RollbackBeforeRestoreKey has a 5 second window. The rollback +// before restore causes the expiration timer to run from the rollback time. +// The license in Offline_RollbackBeforeRestoreKey is expired by the time +// the rollback is undone, while this one has not yet expired. +TEST_F(WvCdmRequestLicenseRollbackTest, + Offline_LongerDurationRollbackBeforeRestoreKey) { + Unprovision(); + Provision(); + + // The default offline asset is "offline_clip2". Substitute '2' for '8'. + // "offline_clip8" has a 15 second expiration. + std::string key_id; + std::string client_auth; + GetOfflineConfiguration(&key_id, &client_auth); + key_id[key_id.size() - 1] = '8'; + + ASSERT_EQ(NO_ERROR, decryptor_->OpenSession(config_.key_system(), nullptr, + kDefaultCdmIdentifier, nullptr, + &session_id_)); + GenerateKeyRequest(key_id, kLicenseTypeOffline); + VerifyKeyRequestResponse(config_.license_server(), client_auth); + + // Verify that we can decrypt a subsample to begin with. + EXPECT_EQ(NO_ERROR, Decrypt(session_id_)); + + CdmKeySetId key_set_id = key_set_id_; + EXPECT_FALSE(key_set_id_.empty()); + decryptor_->CloseSession(session_id_); + + // This number must be > the time between GenerateKeyRequest and this call. + RollbackSystemTime(10 * 1000); + + session_id_.clear(); + decryptor_->OpenSession(config_.key_system(), nullptr, kDefaultCdmIdentifier, + nullptr, &session_id_); + + decryptor_->RestoreKey(session_id_, key_set_id); + + // Verify we can't decrypt. The license start time is in the future. + EXPECT_EQ(DECRYPT_NOT_READY, Decrypt(session_id_)); + + RestoreSystemTime(); + + // Sleep for a little bit to account for the execution time of OpenSession and + // RestoreKey. + std::this_thread::sleep_for(std::chrono::milliseconds(kExpirationWindowMs_)); + + // Verify we can decrypt. + EXPECT_EQ(NO_ERROR, Decrypt(session_id_)); + + // Sleep for a while more. + std::this_thread::sleep_for(std::chrono::milliseconds(kExpirationTimeMs_)); + + // Rollback time + kExpirationWindowMs_ + kExpirationTimeMs_ (~17s) will have + // elapsed. This is greater than the expiration window for longer duration + // licenses (15s). The license should have expired. + EXPECT_EQ(NEED_KEY, Decrypt(session_id_)); + + ASSERT_EQ(NO_ERROR, decryptor_->CloseSession(session_id_)); +} + TEST_F(WvCdmRequestLicenseRollbackTest, Offline_RollbackAndExpireAfterRestoreKey) { Unprovision(); @@ -6631,6 +6694,8 @@ TEST_F(WvCdmRequestLicenseRollbackTest, RestoreSystemTime(); + EXPECT_EQ(NEED_KEY, Decrypt(session_id_)); + ASSERT_EQ(NO_ERROR, decryptor_->CloseSession(session_id_)); } @@ -6665,9 +6730,11 @@ TEST_F(WvCdmRequestLicenseRollbackTest, std::this_thread::sleep_for( std::chrono::milliseconds(kExpirationWithWindowMs_)); + // Verify that we can no longer decrypt a subsample due to key expiration. + EXPECT_EQ(NEED_KEY, Decrypt(session_id_)); + RollbackSystemTime(kExpirationWithWindowMs_); - // Verify that we can no longer decrypt a subsample due to key expiration. EXPECT_EQ(NEED_KEY, Decrypt(session_id_)); RestoreSystemTime(); From 2404500ceb4221fc61f0642cce66ef25580e32eb Mon Sep 17 00:00:00 2001 From: Rahul Frias Date: Fri, 30 Apr 2021 04:52:39 -0700 Subject: [PATCH 2/2] Report Rental duration remaining [ Merge of http://go/wvgerrit/124063 ] LicenseDurationRemaining used to indicate the minimum of rental or license duration till OEMCrypto v16. OEMCrypto v16 onwards it began reporting rental duration alone. This is confusing for app developers and content partners. Keeping LicenseDurationRemaining as apps may depend on it but adding RentalDurationRemaining for clarity. Bug: 186838303 Test: WV unit/integration tests, WvCdmRequestLicenseTest.QueryKeyStatus Change-Id: I6c507150a0945ee36716b4da189f5741b092c0ec --- libwvdrmengine/cdm/core/include/wv_cdm_constants.h | 2 ++ libwvdrmengine/cdm/core/src/policy_engine.cpp | 2 ++ libwvdrmengine/cdm/test/request_license_test.cpp | 7 +++++++ 3 files changed, 11 insertions(+) diff --git a/libwvdrmengine/cdm/core/include/wv_cdm_constants.h b/libwvdrmengine/cdm/core/include/wv_cdm_constants.h index 22c70283..ce2a2714 100644 --- a/libwvdrmengine/cdm/core/include/wv_cdm_constants.h +++ b/libwvdrmengine/cdm/core/include/wv_cdm_constants.h @@ -69,6 +69,8 @@ static const std::string QUERY_KEY_LICENSE_DURATION_REMAINING = "LicenseDurationRemaining"; // non-negative integer denoting seconds static const std::string QUERY_KEY_PLAYBACK_DURATION_REMAINING = "PlaybackDurationRemaining"; // non-negative integer denoting seconds +static const std::string QUERY_KEY_RENTAL_DURATION_REMAINING = + "RentalDurationRemaining"; // non-negative integer denoting seconds static const std::string QUERY_KEY_RENEWAL_SERVER_URL = "RenewalServerUrl"; // url static const std::string QUERY_KEY_OEMCRYPTO_SESSION_ID = diff --git a/libwvdrmengine/cdm/core/src/policy_engine.cpp b/libwvdrmengine/cdm/core/src/policy_engine.cpp index c2be3dd6..019383dc 100644 --- a/libwvdrmengine/cdm/core/src/policy_engine.cpp +++ b/libwvdrmengine/cdm/core/src/policy_engine.cpp @@ -307,6 +307,8 @@ CdmResponseType PolicyEngine::Query(CdmQueryMap* query_response) { : 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] = diff --git a/libwvdrmengine/cdm/test/request_license_test.cpp b/libwvdrmengine/cdm/test/request_license_test.cpp index c60349b4..8fb8173e 100644 --- a/libwvdrmengine/cdm/test/request_license_test.cpp +++ b/libwvdrmengine/cdm/test/request_license_test.cpp @@ -5053,6 +5053,13 @@ TEST_F(WvCdmRequestLicenseTest, QueryKeyStatus) { ss >> remaining_time; ASSERT_FALSE(ss.fail()); EXPECT_LT(0, remaining_time); + itr = query_info.find(wvcdm::QUERY_KEY_RENTAL_DURATION_REMAINING); + ASSERT_TRUE(itr != query_info.end()); + ss.clear(); + ss.str(itr->second); + ss >> remaining_time; + ASSERT_FALSE(ss.fail()); + EXPECT_LT(0, remaining_time); itr = query_info.find(wvcdm::QUERY_KEY_PLAYBACK_DURATION_REMAINING); ASSERT_TRUE(itr != query_info.end()); ss.clear();