diff --git a/libwvdrmengine/cdm/test/request_license_test.cpp b/libwvdrmengine/cdm/test/request_license_test.cpp index a5e1a464..e6794f6a 100644 --- a/libwvdrmengine/cdm/test/request_license_test.cpp +++ b/libwvdrmengine/cdm/test/request_license_test.cpp @@ -3,7 +3,11 @@ // License Agreement. #include +#include + +#include #include +#include #include #include @@ -4873,7 +4877,7 @@ TEST_F(WvCdmRequestLicenseTest, SessionKeyChangeNotificationTest) { session_id_, AllOf(Each(Pair(_, kKeyStatusUsable)), Not(IsEmpty())), true)) .WillOnce(Invoke(&decrypt_callback, &DecryptCallbackTester::Decrypt)); -; + EXPECT_CALL(listener, OnExpirationUpdate(session_id_, _)); const std::string kCpKeyId = a2bs_hex( @@ -5363,4 +5367,273 @@ TEST_F(WvCdmRequestLicenseTest, DISABLED_DecryptPathTest) { decryptor_.CloseSession(session_id_); } +class WvCdmRequestLicenseRollbackTest + : public WvCdmRequestLicenseTest, + public ::testing::WithParamInterface { + public: + WvCdmRequestLicenseRollbackTest() { + SubSampleInfo* data = &single_encrypted_sub_sample_short_expiry; + decrypt_buffer_.resize(data->encrypt_data.size()); + decryption_parameters_ = CdmDecryptionParameters( + &data->key_id, &data->encrypt_data.front(), data->encrypt_data.size(), + &data->iv, data->block_offset, &decrypt_buffer_[0]); + decryption_parameters_.is_encrypted = data->is_encrypted; + decryption_parameters_.is_secure = data->is_secure; + decryption_parameters_.subsample_flags = data->subsample_flags; + validate_key_id_ = data->validate_key_id; + } + ~WvCdmRequestLicenseRollbackTest() {} + + protected: + void RollbackSystemTime(time_t rollback_time_ms) { + if (!in_rollback_state_) { + LOGW("Rolling back system time %d ms.", rollback_time_ms); + wall_time_before_rollback_ = std::chrono::system_clock::now(); + monotonic_time_before_rollback_ = std::chrono::steady_clock::now(); + auto modified_wall_time = wall_time_before_rollback_ - + std::chrono::milliseconds(rollback_time_ms); + timespec modified_wall_time_spec; + modified_wall_time_spec.tv_sec = + std::chrono::duration_cast( + modified_wall_time.time_since_epoch()) + .count(); + modified_wall_time_spec.tv_nsec = + std::chrono::duration_cast( + modified_wall_time.time_since_epoch()) + .count() % + (1000 * 1000 * 1000); + ASSERT_EQ(0, clock_settime(CLOCK_REALTIME, &modified_wall_time_spec)); + in_rollback_state_ = true; + } else { + LOGE("Can't rollback system time more than once without restoring."); + } + } + + void RestoreSystemTime() { + if (in_rollback_state_) { + LOGW("Restoring the system time."); + auto monotonic_time_after_rollback = std::chrono::steady_clock::now(); + auto monotonic_time_diff = + monotonic_time_after_rollback - monotonic_time_before_rollback_; + auto real_time = wall_time_before_rollback_ + monotonic_time_diff; + timespec real_time_spec; + real_time_spec.tv_sec = std::chrono::duration_cast( + real_time.time_since_epoch()) + .count(); + real_time_spec.tv_nsec = + std::chrono::duration_cast( + real_time.time_since_epoch()) + .count() % + (1000 * 1000 * 1000); + ASSERT_EQ(0, clock_settime(CLOCK_REALTIME, &real_time_spec)); + in_rollback_state_ = false; + } else { + LOGW("System time has already been restored."); + } + } + + CdmResponseType Decrypt(CdmSessionId session_id) { + std::fill(decrypt_buffer_.begin(), decrypt_buffer_.end(), 0); + return decryptor_.Decrypt(session_id, validate_key_id_, + decryption_parameters_); + } + + std::chrono::time_point + monotonic_time_before_rollback_; + std::chrono::time_point wall_time_before_rollback_; + bool in_rollback_state_ = false; + const std::string init_data_with_expiry_ = a2bs_hex( + "000000347073736800000000" // blob size and pssh + "EDEF8BA979D64ACEA3C827DCD51D21ED00000014" // Widevine system id + "0801121030313233343536373839616263646566"); // pssh data + // Expiration of the key corresponding to init_data_with_expiry_ with a window + // to ensure expiration. + const time_t kExpirationTimeMs_ = + kSingleEncryptedSubSampleIcpLicenseDurationExpiration * 1000; + const time_t kExpirationWindowMs_ = + kSingleEncryptedSubSampleIcpLicenseExpirationWindow * 1000; + const time_t kExpirationWithWindowMs_ = + (kSingleEncryptedSubSampleIcpLicenseDurationExpiration + + kSingleEncryptedSubSampleIcpLicenseExpirationWindow) * + 1000; + CdmDecryptionParameters decryption_parameters_; + std::vector decrypt_buffer_; + bool validate_key_id_; +}; + +TEST_F(WvCdmRequestLicenseRollbackTest, Streaming_ExpireAfterRollback) { + Unprovision(); + Provision(kLevelDefault); + + ASSERT_EQ(NO_ERROR, decryptor_.OpenSession(config_.key_system(), nullptr, + kDefaultCdmIdentifier, nullptr, + &session_id_)); + GenerateKeyRequest(init_data_with_expiry_, kLicenseTypeStreaming); + VerifyKeyRequestResponse(config_.license_server(), config_.client_auth()); + + // Verify that we can decrypt a subsample to begin with. + EXPECT_EQ(NO_ERROR, Decrypt(session_id_)); + + RollbackSystemTime(kExpirationWithWindowMs_); + + // Elapse time so that the key should now be considered expired. + 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_)); + + RestoreSystemTime(); + + ASSERT_EQ(NO_ERROR, decryptor_.CloseSession(session_id_)); +} + +TEST_F(WvCdmRequestLicenseRollbackTest, Streaming_ExpireBeforeRollback) { + Unprovision(); + Provision(kLevelDefault); + + ASSERT_EQ(NO_ERROR, decryptor_.OpenSession(config_.key_system(), nullptr, + kDefaultCdmIdentifier, nullptr, + &session_id_)); + GenerateKeyRequest(init_data_with_expiry_, kLicenseTypeStreaming); + VerifyKeyRequestResponse(config_.license_server(), config_.client_auth()); + + // Elapse time so that the key should now be considered expired. + std::this_thread::sleep_for( + std::chrono::milliseconds(kExpirationWithWindowMs_)); + + EXPECT_EQ(NEED_KEY, Decrypt(session_id_)); + + RollbackSystemTime(kExpirationWithWindowMs_); + + // Verify that we still can't decrypt even if we rollbacked the clock. + EXPECT_EQ(NEED_KEY, Decrypt(session_id_)); + + RestoreSystemTime(); + + ASSERT_EQ(NO_ERROR, decryptor_.CloseSession(session_id_)); +} + +TEST_F(WvCdmRequestLicenseRollbackTest, Offline_RollbackBeforeRestoreKey) { + Unprovision(); + Provision(kLevelDefault); + + std::string unused_key_id; + std::string client_auth; + GetOfflineConfiguration(&unused_key_id, &client_auth); + + ASSERT_EQ(NO_ERROR, decryptor_.OpenSession(config_.key_system(), nullptr, + kDefaultCdmIdentifier, nullptr, + &session_id_)); + GenerateKeyRequest(init_data_with_expiry_, 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. + 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(kExpirationTimeMs_ / 2)); + + // Verify we can decrypt. + EXPECT_EQ(NO_ERROR, Decrypt(session_id_)); + + ASSERT_EQ(NO_ERROR, decryptor_.CloseSession(session_id_)); +} + +TEST_F(WvCdmRequestLicenseRollbackTest, + Offline_RollbackAndExpireAfterRestoreKey) { + Unprovision(); + Provision(kLevelDefault); + + std::string unused_key_id; + std::string client_auth; + GetOfflineConfiguration(&unused_key_id, &client_auth); + + ASSERT_EQ(NO_ERROR, decryptor_.OpenSession(config_.key_system(), nullptr, + kDefaultCdmIdentifier, nullptr, + &session_id_)); + GenerateKeyRequest(init_data_with_expiry_, kLicenseTypeOffline); + VerifyKeyRequestResponse(config_.license_server(), client_auth); + + CdmKeySetId key_set_id = key_set_id_; + EXPECT_FALSE(key_set_id_.empty()); + decryptor_.CloseSession(session_id_); + + session_id_.clear(); + decryptor_.OpenSession(config_.key_system(), nullptr, kDefaultCdmIdentifier, + nullptr, &session_id_); + EXPECT_EQ(wvcdm::KEY_ADDED, decryptor_.RestoreKey(session_id_, key_set_id)); + + RollbackSystemTime(kExpirationWithWindowMs_); + + // Elapse time so that the key should now be considered expired. + 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_)); + + RestoreSystemTime(); + + ASSERT_EQ(NO_ERROR, decryptor_.CloseSession(session_id_)); +} + +TEST_F(WvCdmRequestLicenseRollbackTest, + Offline_ExpireAndRollbackAfterRestoreKey) { + Unprovision(); + Provision(kLevelDefault); + + std::string unused_key_id; + std::string client_auth; + GetOfflineConfiguration(&unused_key_id, &client_auth); + + ASSERT_EQ(NO_ERROR, decryptor_.OpenSession(config_.key_system(), nullptr, + kDefaultCdmIdentifier, nullptr, + &session_id_)); + GenerateKeyRequest(init_data_with_expiry_, kLicenseTypeOffline); + VerifyKeyRequestResponse(config_.license_server(), client_auth); + + CdmKeySetId key_set_id = key_set_id_; + EXPECT_FALSE(key_set_id_.empty()); + decryptor_.CloseSession(session_id_); + + session_id_.clear(); + decryptor_.OpenSession(config_.key_system(), nullptr, kDefaultCdmIdentifier, + nullptr, &session_id_); + EXPECT_EQ(wvcdm::KEY_ADDED, decryptor_.RestoreKey(session_id_, key_set_id)); + + // Elapse time so that the key should now be considered expired. + std::this_thread::sleep_for( + std::chrono::milliseconds(kExpirationWithWindowMs_)); + + RollbackSystemTime(kExpirationWithWindowMs_); + + // Verify that we can no longer decrypt a subsample due to key expiration. + EXPECT_EQ(NEED_KEY, Decrypt(session_id_)); + + RestoreSystemTime(); + + ASSERT_EQ(NO_ERROR, decryptor_.CloseSession(session_id_)); +} + } // namespace wvcdm