Update TimeRollbackPrevention unit test
Merge from Widevine repo of http://go/wvgerrit/100110 The unit test TimeRollbackPrevention was broken for several reasons. This CL reduces the test to its most basic functionality and updates it to be compatible with a v16 oemcrypto. This CL also adjusts the fake clock used by the buildbot to fake sleeping backwards, so that the TimeRollbackPrevention test can also be run on the buildbot. Bug: 155773482 Bug: 79422351 Test: unit tests on buildbot, and on flame w/v16 modmock Change-Id: I3027018b17b738281989e63ae6b0729757217d05
This commit is contained in:
@@ -14,12 +14,6 @@
|
||||
#include <openssl/x509.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <windows.h>
|
||||
#else
|
||||
# include <sys/time.h>
|
||||
#endif
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
@@ -122,37 +116,6 @@ const size_t kLargeMessageSize[] = { 8*KiB, 8*KiB, 16*KiB, 32*KiB};
|
||||
// const size_t kAV1NumberSubsamples[] = { 72, 144, 288, 576};
|
||||
// clang-format on
|
||||
|
||||
/** @return The Unix time of the given time point. */
|
||||
template <typename Duration>
|
||||
uint64_t UnixTime(
|
||||
const std::chrono::time_point<std::chrono::system_clock, Duration>& point) {
|
||||
return point.time_since_epoch() / std::chrono::seconds(1);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
using NativeTime = SYSTEMTIME;
|
||||
#else
|
||||
using NativeTime = timeval;
|
||||
#endif
|
||||
|
||||
void AddNativeTime(int64_t delta_seconds, NativeTime* time) {
|
||||
#ifdef _WIN32
|
||||
// See remarks from this for why this series is used.
|
||||
// https://msdn.microsoft.com/en-us/f77cdf86-0f97-4a89-b565-95b46fa7d65b
|
||||
FILETIME file_time;
|
||||
ASSERT_TRUE(SystemTimeToFileTime(time, &file_time));
|
||||
uint64_t long_time = static_cast<uint64_t>(file_time.dwLowDateTime) |
|
||||
(static_cast<uint64_t>(file_time.dwHighDateTime) << 32);
|
||||
long_time +=
|
||||
delta_seconds * 1e7; // long_time is in 100-nanosecond intervals.
|
||||
file_time.dwLowDateTime = long_time & ((1ull << 32) - 1);
|
||||
file_time.dwHighDateTime = long_time >> 32;
|
||||
ASSERT_TRUE(FileTimeToSystemTime(&file_time, time));
|
||||
#else
|
||||
time->tv_sec += delta_seconds;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class OEMCryptoClientTest : public ::testing::Test, public SessionUtil {
|
||||
@@ -6215,44 +6178,12 @@ class OEMCryptoUsageTableTestWallClock : public OEMCryptoUsageTableTest {
|
||||
public:
|
||||
void SetUp() override {
|
||||
OEMCryptoUsageTableTest::SetUp();
|
||||
did_change_system_time_ = false;
|
||||
test_start_steady_ = steady_clock_.now();
|
||||
#ifdef _WIN32
|
||||
GetSystemTime(&test_start_wall_);
|
||||
#else
|
||||
ASSERT_EQ(0, gettimeofday(&test_start_wall_, nullptr));
|
||||
#endif
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
if (did_change_system_time_) {
|
||||
const auto delta = steady_clock_.now() - test_start_steady_;
|
||||
const int64_t delta_sec = delta / std::chrono::seconds(1);
|
||||
ASSERT_NO_FATAL_FAILURE(SetWallTimeDelta(delta_sec));
|
||||
}
|
||||
wvcdm::TestSleep::ResetRollback();
|
||||
OEMCryptoUsageTableTest::TearDown();
|
||||
}
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Sets the current wall-clock time to a delta based on the start of the
|
||||
* test.
|
||||
*/
|
||||
void SetWallTimeDelta(int64_t delta_seconds) {
|
||||
did_change_system_time_ = true;
|
||||
NativeTime time = test_start_wall_;
|
||||
ASSERT_NO_FATAL_FAILURE(AddNativeTime(delta_seconds, &time));
|
||||
#ifdef _WIN32
|
||||
ASSERT_TRUE(SetSystemTime(&time));
|
||||
#else
|
||||
ASSERT_EQ(0, settimeofday(&time, nullptr));
|
||||
#endif
|
||||
}
|
||||
|
||||
std::chrono::steady_clock steady_clock_;
|
||||
bool did_change_system_time_;
|
||||
NativeTime test_start_wall_;
|
||||
std::chrono::time_point<std::chrono::steady_clock> test_start_steady_;
|
||||
};
|
||||
|
||||
// NOTE: This test needs root access since clock_settime messes with the system
|
||||
@@ -6261,61 +6192,104 @@ class OEMCryptoUsageTableTestWallClock : public OEMCryptoUsageTableTest {
|
||||
// We don't test roll-forward protection or instances where the user rolls back
|
||||
// the time to the last decrypt call since this requires hardware-secure clocks
|
||||
// to guarantee.
|
||||
//
|
||||
// This test overlaps two tests in parallel because they each have several
|
||||
// seconds of sleeping, then we roll the system clock back, and then we sleep
|
||||
// some more.
|
||||
// For the first test, we use entry1. The playback duration is 6 short
|
||||
// intervals. We play for 3, roll the clock back 2, and then play for 3 more.
|
||||
// We then sleep until after the allowed playback duration and try to play. If
|
||||
// OEMCrypto allows the rollback, then there is only 5 intervals, which is
|
||||
// legal. But if OEMCrypto forbids the rollback, then there is 8 intervals of
|
||||
// playback, which is not legal.
|
||||
//
|
||||
// For the second test, we use entry2. The rental duration is 6 short
|
||||
// intervals. The times are the same as for entry1, except we do not start
|
||||
// playback for entry2 until the end.
|
||||
|
||||
// clang-format off
|
||||
// [--][--][--][--][--][--][--] -- playback or rental limit.
|
||||
//
|
||||
// Here's what the system clock sees with rollback:
|
||||
// [--][--][--] 3 short intervals of playback or sleep
|
||||
// <------> Rollback 2 short intervals.
|
||||
// [--][--][--] 3 short intervals of playback or sleep
|
||||
// [--] 1 short intervals of sleep.
|
||||
//
|
||||
// Here's what the system clock sees without rollback:
|
||||
// [--][--][--] 3 short intervals of playback or sleep
|
||||
// [--][--][--] 3 short intervals of playback or sleep
|
||||
// [--][--]X 2 short intervals of sleep.
|
||||
//
|
||||
// |<---------------------------->| 8 short intervals from license received
|
||||
// until pst reports generated.
|
||||
// clang-format on
|
||||
|
||||
TEST_P(OEMCryptoUsageTableTestWallClock, TimeRollbackPrevention) {
|
||||
cout << "This test temporarily rolls back the system time in order to verify "
|
||||
<< "that the usage report accounts for the change. It then rolls "
|
||||
<< "the time back forward to the absolute time." << endl;
|
||||
LicenseWithUsageEntry entry;
|
||||
entry.MakeOfflineAndClose(this);
|
||||
Session& s = entry.session();
|
||||
std::chrono::system_clock wall_clock;
|
||||
std::chrono::steady_clock monotonic_clock;
|
||||
const auto loaded = wall_clock.now();
|
||||
<< "that the usage report accounts for the change. After the test, it "
|
||||
<< "rolls the clock back forward." << endl;
|
||||
constexpr int kRollBackTime = kShortSleep * 2;
|
||||
constexpr int kPlaybackCount = 3;
|
||||
constexpr int kTotalTime = kShortSleep * 8;
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this));
|
||||
const auto first_decrypt = wall_clock.now();
|
||||
// Monotonic clock can't be changed. We use this since system clock will be
|
||||
// unreliable.
|
||||
const auto first_decrypt_monotonic = monotonic_clock.now();
|
||||
ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR());
|
||||
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
||||
ASSERT_NO_FATAL_FAILURE(s.close());
|
||||
LicenseWithUsageEntry entry1;
|
||||
entry1.license_messages()
|
||||
.core_response()
|
||||
.timer_limits.total_playback_duration_seconds = 7 * kShortSleep;
|
||||
entry1.MakeOfflineAndClose(this);
|
||||
Session& s1 = entry1.session();
|
||||
ASSERT_NO_FATAL_FAILURE(entry1.OpenAndReload(this));
|
||||
|
||||
// Imitate playback.
|
||||
wvcdm::TestSleep::Sleep(kLongDuration * 2);
|
||||
LicenseWithUsageEntry entry2;
|
||||
entry2.license_messages()
|
||||
.core_response()
|
||||
.timer_limits.rental_duration_seconds = 7 * kShortSleep;
|
||||
entry2.MakeOfflineAndClose(this);
|
||||
Session& s2 = entry2.session();
|
||||
ASSERT_NO_FATAL_FAILURE(entry2.OpenAndReload(this));
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this));
|
||||
ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR());
|
||||
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
||||
ASSERT_NO_FATAL_FAILURE(s.close());
|
||||
// Start with three short intervals of playback for entry1.
|
||||
for (int i = 0; i < kPlaybackCount; i++) {
|
||||
ASSERT_NO_FATAL_FAILURE(entry1.TestDecryptCTR());
|
||||
wvcdm::TestSleep::Sleep(kShortSleep);
|
||||
ASSERT_NO_FATAL_FAILURE(entry1.TestDecryptCTR());
|
||||
}
|
||||
|
||||
// Rollback the wall clock time.
|
||||
cout << "Rolling the system time back..." << endl;
|
||||
ASSERT_TRUE(wvcdm::TestSleep::RollbackSystemTime(kRollBackTime));
|
||||
|
||||
// Three more short intervals of playback after the rollback.
|
||||
for (int i = 0; i < kPlaybackCount; i++) {
|
||||
ASSERT_NO_FATAL_FAILURE(entry1.TestDecryptCTR());
|
||||
wvcdm::TestSleep::Sleep(kShortSleep);
|
||||
ASSERT_NO_FATAL_FAILURE(entry1.TestDecryptCTR());
|
||||
}
|
||||
|
||||
// One short interval of sleep to push us past the 6 interval duration.
|
||||
wvcdm::TestSleep::Sleep(2 * kShortSleep);
|
||||
|
||||
// Should not be able to continue playback in entry1.
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
SetWallTimeDelta(-static_cast<int64_t>(kLongDuration) * 10));
|
||||
|
||||
// Try to playback again.
|
||||
ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this));
|
||||
const auto third_decrypt_monotonic = monotonic_clock.now();
|
||||
ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR());
|
||||
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
||||
ASSERT_NO_FATAL_FAILURE(s.close());
|
||||
Test_PST_Report expected(entry.pst(), kActive);
|
||||
|
||||
// Restore wall clock to its original position to verify that OEMCrypto does
|
||||
// not report negative times.
|
||||
const auto test_duration = third_decrypt_monotonic - first_decrypt_monotonic;
|
||||
cout << "Rolling the system time forward to the absolute time..." << endl;
|
||||
entry1.TestDecryptCTR(false, OEMCrypto_ERROR_KEY_EXPIRED));
|
||||
// Should not be able to start playback in entry2.
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
SetWallTimeDelta(test_duration / std::chrono::seconds(1)));
|
||||
// Need to update time created since the verification checks the time of PST
|
||||
// report creation.
|
||||
expected.time_created = UnixTime(wall_clock.now());
|
||||
entry2.TestDecryptCTR(true, OEMCrypto_ERROR_KEY_EXPIRED));
|
||||
|
||||
const auto end_time = first_decrypt + test_duration;
|
||||
ASSERT_NO_FATAL_FAILURE(s.VerifyReport(
|
||||
expected, UnixTime(loaded), UnixTime(first_decrypt), UnixTime(end_time)));
|
||||
ASSERT_NO_FATAL_FAILURE(s.close());
|
||||
// Now we look at the usage reports:
|
||||
ASSERT_NO_FATAL_FAILURE(s1.UpdateUsageEntry(&encrypted_usage_header_));
|
||||
ASSERT_NO_FATAL_FAILURE(s2.UpdateUsageEntry(&encrypted_usage_header_));
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(s1.GenerateReport(entry1.pst()));
|
||||
wvcdm::Unpacked_PST_Report report1 = s1.pst_report();
|
||||
EXPECT_EQ(report1.status(), kActive);
|
||||
EXPECT_GE(report1.seconds_since_license_received(), kTotalTime);
|
||||
EXPECT_GE(report1.seconds_since_first_decrypt(), kTotalTime);
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(s2.GenerateReport(entry2.pst()));
|
||||
wvcdm::Unpacked_PST_Report report2 = s2.pst_report();
|
||||
EXPECT_EQ(report2.status(), kUnused);
|
||||
EXPECT_GE(report2.seconds_since_license_received(), kTotalTime);
|
||||
}
|
||||
|
||||
// Verify that a large PST can be used with usage table entries.
|
||||
|
||||
Reference in New Issue
Block a user