From 6bd5c564d82cc3d6e99d0ce506d830930889dd93 Mon Sep 17 00:00:00 2001 From: Srujan Gaddam Date: Wed, 16 Jan 2019 16:51:02 -0800 Subject: [PATCH] Test to ensure rollback doesn't affect key duration Bug: b/78359032 Merge of http://go/wvgerrit/70544 Test: Android unit + integration tests/manual checking of system time to make sure it's restored correctly Integration tests are added to make sure that clock rollback of the device system time does not affect key duration for both streaming and offline licenses. These tests require root access as they modify system time and restore them. Change-Id: I3e1802205e2fc2056093c1e39b0ff6e0a8816abc --- .../cdm/test/request_license_test.cpp | 275 +++++++++++++++++- 1 file changed, 274 insertions(+), 1 deletion(-) 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