Update TimeRollbackPrevention unit test am: 75575418d0 am: 7cd14be01d am: 51cf7e4668 am: 40e2e7fa08
Change-Id: I21b29668ed8a9b39e8a7679ed65281d14587bba3
This commit is contained in:
committed by
Automerger Merge Worker
commit
cd4d4f564a
@@ -4,16 +4,26 @@
|
|||||||
|
|
||||||
#include "test_sleep.h"
|
#include "test_sleep.h"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
# include <windows.h>
|
||||||
|
#else
|
||||||
|
# include <sys/types.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
||||||
#include "clock.h"
|
#include "clock.h"
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
namespace wvcdm {
|
namespace wvcdm {
|
||||||
|
|
||||||
bool TestSleep::real_sleep_ = true;
|
bool TestSleep::real_sleep_ = true;
|
||||||
TestSleep::CallBack* TestSleep::callback_ = nullptr;
|
TestSleep::CallBack* TestSleep::callback_ = nullptr;
|
||||||
|
int TestSleep::total_clock_rollback_ = 0;
|
||||||
|
|
||||||
void TestSleep::Sleep(unsigned int seconds) {
|
void TestSleep::Sleep(unsigned int seconds) {
|
||||||
int64_t milliseconds = 1000 * seconds;
|
int64_t milliseconds = 1000 * seconds;
|
||||||
@@ -23,10 +33,10 @@ void TestSleep::Sleep(unsigned int seconds) {
|
|||||||
// total since the start, and then compare to a running total of sleep
|
// total since the start, and then compare to a running total of sleep
|
||||||
// calls. We sleep for approximately x second, and then advance the clock by
|
// calls. We sleep for approximately x second, and then advance the clock by
|
||||||
// the amount of time that has actually passed.
|
// the amount of time that has actually passed.
|
||||||
static auto start_real = std::chrono::steady_clock().now();
|
static auto start_real = std::chrono::system_clock().now();
|
||||||
static int64_t fake_clock = 0;
|
static int64_t fake_clock = 0;
|
||||||
sleep(seconds);
|
sleep(seconds);
|
||||||
auto now_real = std::chrono::steady_clock().now();
|
auto now_real = std::chrono::system_clock().now();
|
||||||
int64_t total_real = (now_real - start_real) / std::chrono::milliseconds(1);
|
int64_t total_real = (now_real - start_real) / std::chrono::milliseconds(1);
|
||||||
// We want to advance the fake clock by the difference between the real
|
// We want to advance the fake clock by the difference between the real
|
||||||
// clock, and the previous value on the fake clock.
|
// clock, and the previous value on the fake clock.
|
||||||
@@ -41,4 +51,98 @@ void TestSleep::SyncFakeClock() {
|
|||||||
Sleep(0);
|
Sleep(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TestSleep::RollbackSystemTime(int seconds) {
|
||||||
|
if (real_sleep_) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
// See remarks from this for why this series is used.
|
||||||
|
// https://msdn.microsoft.com/en-us/f77cdf86-0f97-4a89-b565-95b46fa7d65b
|
||||||
|
SYSTEMTIME time;
|
||||||
|
GetSystemTime(&time);
|
||||||
|
FILETIME file_time;
|
||||||
|
if (!SystemTimeToFileTime(time, &file_time)) return false;
|
||||||
|
uint64_t long_time =
|
||||||
|
static_cast<uint64_t>(file_time.dwLowDateTime) |
|
||||||
|
(static_cast<uint64_t>(file_time.dwHighDateTime) << 32);
|
||||||
|
long_time += static_cast<uint64_t>(delta_seconds) *
|
||||||
|
1e7; // long_time is in 100-nanosecond intervals.
|
||||||
|
file_time.dwLowDateTime = long_time & ((1ull << 32) - 1);
|
||||||
|
file_time.dwHighDateTime = long_time >> 32;
|
||||||
|
if (!FileTimeToSystemTime(&file_time, &time)) return false;
|
||||||
|
if (!SetSystemTime(&time)) return false;
|
||||||
|
#else
|
||||||
|
auto time = std::chrono::system_clock::now();
|
||||||
|
auto modified_time = time - std::chrono::seconds(seconds);
|
||||||
|
;
|
||||||
|
timespec time_spec;
|
||||||
|
time_spec.tv_sec = std::chrono::duration_cast<std::chrono::seconds>(
|
||||||
|
modified_time.time_since_epoch())
|
||||||
|
.count();
|
||||||
|
time_spec.tv_nsec = std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||||
|
modified_time.time_since_epoch())
|
||||||
|
.count() %
|
||||||
|
(1000 * 1000 * 1000);
|
||||||
|
if (clock_settime(CLOCK_REALTIME, &time_spec)) {
|
||||||
|
LOGE("Error setting clock: %s", strerror(errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
} // end if(real_sleep_)...
|
||||||
|
|
||||||
|
// For both real and fake sleep we still update the callback and we still keep
|
||||||
|
// track of the total amount of time slept.
|
||||||
|
total_clock_rollback_ += seconds;
|
||||||
|
if (callback_ != nullptr) callback_->ElapseTime(-1000 * seconds);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TestSleep::CanChangeSystemTime() {
|
||||||
|
// If we are using a fake clock, then we can move the clock backwards by
|
||||||
|
// just going backwards.
|
||||||
|
// ElapseTime.
|
||||||
|
if (!real_sleep_) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#ifdef _WIN32
|
||||||
|
LUID desired_id;
|
||||||
|
if (!LookupPrivilegeValue(nullptr, SE_SYSTEMTIME_NAME, &desired_id)) {
|
||||||
|
LOGE("Win32 time rollback: no SYSTEMTIME permission.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
HANDLE token;
|
||||||
|
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &token)) {
|
||||||
|
LOGE("Win32 time rollback: cannot access process token.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::unique_ptr<void, decltype(&CloseHandle)> safe_token(token, &CloseHandle);
|
||||||
|
|
||||||
|
// This queries all the permissions given to the token to determine if we can
|
||||||
|
// change the system time. Note this is subtly different from PrivilegeCheck
|
||||||
|
// as that only checks "enabled" privileges; even with admin rights, the
|
||||||
|
// privilege is default disabled, even when granted.
|
||||||
|
|
||||||
|
DWORD size = 0;
|
||||||
|
// Determine how big we need to allocate first.
|
||||||
|
GetTokenInformation(token, TokenPrivileges, nullptr, 0, &size);
|
||||||
|
// Since TOKEN_PRIVILEGES uses a variable-length array, we need to use malloc
|
||||||
|
std::unique_ptr<TOKEN_PRIVILEGES, decltype(&free)> privileges(
|
||||||
|
(TOKEN_PRIVILEGES*)malloc(size), &free);
|
||||||
|
if (privileges && GetTokenInformation(token, TokenPrivileges,
|
||||||
|
privileges.get(), size, &size)) {
|
||||||
|
for (int i = 0; i < privileges->PrivilegeCount; i++) {
|
||||||
|
if (privileges->Privileges[i].Luid.HighPart == desired_id.HighPart &&
|
||||||
|
privileges->Privileges[i].Luid.LowPart == desired_id.LowPart) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOGE("Win32 time rollback: cannot set system time.");
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
|
// Otherwise, the test needs to be run as root.
|
||||||
|
const uid_t uid = getuid();
|
||||||
|
if (uid == 0) return true;
|
||||||
|
LOGE("Unix time rollback: not running as root (uid=%u.", uid);
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
} // namespace wvcdm
|
} // namespace wvcdm
|
||||||
|
|||||||
@@ -22,8 +22,9 @@ class TestSleep {
|
|||||||
virtual ~CallBack(){};
|
virtual ~CallBack(){};
|
||||||
};
|
};
|
||||||
|
|
||||||
// If real_sleep_ is true, then this sleeps for |seconds| of time.
|
// If real_sleep_ is true, then this sleeps for |seconds| of time. If
|
||||||
// If the callback exists, this calls the callback.
|
// real_sleep_ is false, then the fake clock is advanced by |seconds|. If the
|
||||||
|
// callback exists, this calls the callback.
|
||||||
static void Sleep(unsigned int seconds);
|
static void Sleep(unsigned int seconds);
|
||||||
|
|
||||||
// If we are using a real clock and a fake clock, then the real clock advances
|
// If we are using a real clock and a fake clock, then the real clock advances
|
||||||
@@ -33,8 +34,30 @@ class TestSleep {
|
|||||||
// failing due to this drift.
|
// failing due to this drift.
|
||||||
static void SyncFakeClock();
|
static void SyncFakeClock();
|
||||||
|
|
||||||
static void set_real_sleep(bool real_sleep) { real_sleep_ = real_sleep; }
|
// Roll the system clock back by |seconds|. Returns true on success. A well
|
||||||
|
// mannered test will call CanChangeSystemTime before attempting to call this
|
||||||
|
// function and then assert that this is true. This function should *NOT* roll
|
||||||
|
// back the clock used by OEMCrypto -- in fact, there are several tests that
|
||||||
|
// verify this function does not roll back the clock used by OEMCrypto.
|
||||||
|
static bool RollbackSystemTime(int seconds);
|
||||||
|
|
||||||
|
// Roll the system clock forward to undo all previous calls to
|
||||||
|
// RollBackSystemTime. Returns true on success.
|
||||||
|
static bool ResetRollback() {
|
||||||
|
return total_clock_rollback_ == 0 ||
|
||||||
|
RollbackSystemTime(-total_clock_rollback_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the system time can be rolled back. This is true on some
|
||||||
|
// devices if the tests are run as root. It is also true when using a fake
|
||||||
|
// clock with the reference version of OEMCrypto. This function is about the
|
||||||
|
// system clock, *NOT* the clock used by OEMCrypto.
|
||||||
|
static bool CanChangeSystemTime();
|
||||||
|
|
||||||
|
static void set_real_sleep(bool real_sleep) { real_sleep_ = real_sleep; }
|
||||||
|
static bool real_sleep() { return real_sleep_; }
|
||||||
|
|
||||||
|
// The callback is notified whenever sleep is called.
|
||||||
static void set_callback(CallBack* callback) { callback_ = callback; }
|
static void set_callback(CallBack* callback) { callback_ = callback; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -42,6 +65,9 @@ class TestSleep {
|
|||||||
static bool real_sleep_;
|
static bool real_sleep_;
|
||||||
// Called when the clock should advance.
|
// Called when the clock should advance.
|
||||||
static CallBack* callback_;
|
static CallBack* callback_;
|
||||||
|
// The sum of all calls to RollBackSystemTime. Kept so we can undo all changes
|
||||||
|
// at the end of a test.
|
||||||
|
static int total_clock_rollback_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace wvcdm
|
} // namespace wvcdm
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ int64_t CryptoEngine::MonotonicTime() {
|
|||||||
wvcdm::Clock().GetCurrentTime() + offline_time_info_.rollback_offset;
|
wvcdm::Clock().GetCurrentTime() + offline_time_info_.rollback_offset;
|
||||||
static int64_t then = now;
|
static int64_t then = now;
|
||||||
if (now < then) {
|
if (now < then) {
|
||||||
|
LOGW("Clock rollback detected: %lld seconds", then - now);
|
||||||
offline_time_info_.rollback_offset += then - now;
|
offline_time_info_.rollback_offset += then - now;
|
||||||
now = then;
|
now = then;
|
||||||
}
|
}
|
||||||
@@ -138,31 +139,16 @@ bool CryptoEngine::LoadOfflineTimeInfo(const std::string& file_path) {
|
|||||||
memset(&offline_time_info_, 0, sizeof(TimeInfo));
|
memset(&offline_time_info_, 0, sizeof(TimeInfo));
|
||||||
wvcdm::FileSystem* file_system = file_system_.get();
|
wvcdm::FileSystem* file_system = file_system_.get();
|
||||||
if (file_system->Exists(file_path)) {
|
if (file_system->Exists(file_path)) {
|
||||||
std::vector<uint8_t> encrypted_buffer(sizeof(TimeInfo));
|
|
||||||
std::vector<uint8_t> clear_buffer(sizeof(TimeInfo));
|
|
||||||
|
|
||||||
KeyboxError error_code = ValidateKeybox();
|
|
||||||
if (error_code != NO_ERROR) {
|
|
||||||
LOGE("Keybox is invalid: %d", error_code);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Use the device key for encrypt/decrypt.
|
|
||||||
const std::vector<uint8_t>& key = DeviceRootKey();
|
|
||||||
std::unique_ptr<wvcdm::File> file =
|
std::unique_ptr<wvcdm::File> file =
|
||||||
file_system->Open(file_path, wvcdm::FileSystem::kReadOnly);
|
file_system->Open(file_path, wvcdm::FileSystem::kReadOnly);
|
||||||
if (!file) {
|
if (!file) {
|
||||||
LOGE("File open failed: %s", file_path.c_str());
|
// This error is expected at first initialization.
|
||||||
|
LOGE("File open failed (this is expected on first initialization): %s",
|
||||||
|
file_path.c_str());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Load time info from previous call.
|
// Load time info from previous call.
|
||||||
file->Read(reinterpret_cast<char*>(&encrypted_buffer[0]), sizeof(TimeInfo));
|
file->Read(reinterpret_cast<char*>(&offline_time_info_), sizeof(TimeInfo));
|
||||||
// Decrypt the encrypted TimeInfo buffer.
|
|
||||||
AES_KEY aes_key;
|
|
||||||
AES_set_decrypt_key(&key[0], 128, &aes_key);
|
|
||||||
std::vector<uint8_t> iv(wvoec::KEY_IV_SIZE, 0);
|
|
||||||
AES_cbc_encrypt(&encrypted_buffer[0], &clear_buffer[0], sizeof(TimeInfo),
|
|
||||||
&aes_key, iv.data(), AES_DECRYPT);
|
|
||||||
memcpy(&offline_time_info_, &clear_buffer[0], sizeof(TimeInfo));
|
|
||||||
|
|
||||||
// Detect offline time rollback after loading from disk.
|
// Detect offline time rollback after loading from disk.
|
||||||
// Add any time offsets in the past to the current time.
|
// Add any time offsets in the past to the current time.
|
||||||
@@ -191,34 +177,16 @@ bool CryptoEngine::SaveOfflineTimeInfo(const std::string& file_path) {
|
|||||||
if (current_time > offline_time_info_.previous_time)
|
if (current_time > offline_time_info_.previous_time)
|
||||||
offline_time_info_.previous_time = current_time;
|
offline_time_info_.previous_time = current_time;
|
||||||
|
|
||||||
KeyboxError error_code = ValidateKeybox();
|
|
||||||
if (error_code != NO_ERROR) {
|
|
||||||
LOGE("Keybox is invalid: %d", error_code);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Use the device key for encrypt/decrypt.
|
|
||||||
const std::vector<uint8_t>& key = DeviceRootKey();
|
|
||||||
std::vector<uint8_t> encrypted_buffer(sizeof(TimeInfo));
|
|
||||||
std::vector<uint8_t> clear_buffer(sizeof(TimeInfo));
|
|
||||||
|
|
||||||
// Copy updated data and encrypt the buffer.
|
|
||||||
memcpy(&clear_buffer[0], &offline_time_info_, sizeof(TimeInfo));
|
|
||||||
AES_KEY aes_key;
|
|
||||||
AES_set_encrypt_key(&key[0], 128, &aes_key);
|
|
||||||
std::vector<uint8_t> iv(wvoec::KEY_IV_SIZE, 0);
|
|
||||||
AES_cbc_encrypt(&clear_buffer[0], &encrypted_buffer[0], sizeof(TimeInfo),
|
|
||||||
&aes_key, iv.data(), AES_ENCRYPT);
|
|
||||||
|
|
||||||
std::unique_ptr<wvcdm::File> file;
|
std::unique_ptr<wvcdm::File> file;
|
||||||
wvcdm::FileSystem* file_system = file_system_.get();
|
wvcdm::FileSystem* file_system = file_system_.get();
|
||||||
// Write the encrypted buffer to disk.
|
// Write the current time and offset to disk.
|
||||||
file = file_system->Open(
|
file = file_system->Open(
|
||||||
file_path, wvcdm::FileSystem::kCreate | wvcdm::FileSystem::kTruncate);
|
file_path, wvcdm::FileSystem::kCreate | wvcdm::FileSystem::kTruncate);
|
||||||
if (!file) {
|
if (!file) {
|
||||||
LOGE("File open failed: %s", file_path.c_str());
|
LOGE("File open failed: %s", file_path.c_str());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
file->Write(reinterpret_cast<char*>(&encrypted_buffer[0]), sizeof(TimeInfo));
|
file->Write(reinterpret_cast<char*>(&offline_time_info_), sizeof(TimeInfo));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,58 +8,15 @@
|
|||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
# include <windows.h>
|
|
||||||
#else
|
|
||||||
# include <sys/types.h>
|
|
||||||
# include <unistd.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
#include "oec_test_data.h"
|
#include "oec_test_data.h"
|
||||||
|
#include "test_sleep.h"
|
||||||
|
|
||||||
namespace wvoec {
|
namespace wvoec {
|
||||||
|
|
||||||
DeviceFeatures global_features;
|
DeviceFeatures global_features;
|
||||||
|
|
||||||
bool CanChangeTime() {
|
|
||||||
#ifdef _WIN32
|
|
||||||
LUID desired_id;
|
|
||||||
if (!LookupPrivilegeValue(nullptr, SE_SYSTEMTIME_NAME, &desired_id))
|
|
||||||
return false;
|
|
||||||
HANDLE token;
|
|
||||||
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &token))
|
|
||||||
return false;
|
|
||||||
std::unique_ptr<void, decltype(&CloseHandle)> safe_token(token, &CloseHandle);
|
|
||||||
|
|
||||||
// This queries all the permissions given to the token to determine if we can
|
|
||||||
// change the system time. Note this is subtly different from PrivilegeCheck
|
|
||||||
// as that only checks "enabled" privileges; even with admin rights, the
|
|
||||||
// privilege is default disabled, even when granted.
|
|
||||||
|
|
||||||
DWORD size = 0;
|
|
||||||
// Determine how big we need to allocate first.
|
|
||||||
GetTokenInformation(token, TokenPrivileges, nullptr, 0, &size);
|
|
||||||
// Since TOKEN_PRIVILEGES uses a variable-length array, we need to use malloc
|
|
||||||
std::unique_ptr<TOKEN_PRIVILEGES, decltype(&free)> privileges(
|
|
||||||
(TOKEN_PRIVILEGES*)malloc(size), &free);
|
|
||||||
if (privileges && GetTokenInformation(token, TokenPrivileges,
|
|
||||||
privileges.get(), size, &size)) {
|
|
||||||
for (int i = 0; i < privileges->PrivilegeCount; i++) {
|
|
||||||
if (privileges->Privileges[i].Luid.HighPart == desired_id.HighPart &&
|
|
||||||
privileges->Privileges[i].Luid.LowPart == desired_id.LowPart) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
#else
|
|
||||||
return getuid() == 0;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeviceFeatures::Initialize() {
|
void DeviceFeatures::Initialize() {
|
||||||
if (initialized_) return;
|
if (initialized_) return;
|
||||||
uses_keybox = false;
|
uses_keybox = false;
|
||||||
@@ -187,8 +144,11 @@ std::string DeviceFeatures::RestrictFilter(const std::string& initial_filter) {
|
|||||||
// clang-format on
|
// clang-format on
|
||||||
// Some tests may require root access. If user is not root, filter these tests
|
// Some tests may require root access. If user is not root, filter these tests
|
||||||
// out.
|
// out.
|
||||||
if (!CanChangeTime()) {
|
if (!wvcdm::TestSleep::CanChangeSystemTime()) {
|
||||||
|
printf("Filtering out TimeRollbackPrevention.\n");
|
||||||
FilterOut(&filter, "*TimeRollbackPrevention*");
|
FilterOut(&filter, "*TimeRollbackPrevention*");
|
||||||
|
} else {
|
||||||
|
printf("Can change time. I will run TimeRollbackPrevention.\n");
|
||||||
}
|
}
|
||||||
// Performance tests take a long time. Filter them out if they are not
|
// Performance tests take a long time. Filter them out if they are not
|
||||||
// specifically requested.
|
// specifically requested.
|
||||||
|
|||||||
@@ -1471,7 +1471,10 @@ void Session::VerifyReport(Test_PST_Report expected,
|
|||||||
int64_t time_first_decrypt,
|
int64_t time_first_decrypt,
|
||||||
int64_t time_last_decrypt) {
|
int64_t time_last_decrypt) {
|
||||||
const int64_t now = wvcdm::Clock().GetCurrentTime();
|
const int64_t now = wvcdm::Clock().GetCurrentTime();
|
||||||
expected.seconds_since_license_received = now - time_license_received;
|
expected.seconds_since_license_received =
|
||||||
|
(time_license_received > 0 && time_license_received < now)
|
||||||
|
? now - time_license_received
|
||||||
|
: 0;
|
||||||
expected.seconds_since_first_decrypt =
|
expected.seconds_since_first_decrypt =
|
||||||
(time_first_decrypt > 0 && time_first_decrypt < now)
|
(time_first_decrypt > 0 && time_first_decrypt < now)
|
||||||
? now - time_first_decrypt
|
? now - time_first_decrypt
|
||||||
|
|||||||
@@ -14,12 +14,6 @@
|
|||||||
#include <openssl/x509.h>
|
#include <openssl/x509.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
# include <windows.h>
|
|
||||||
#else
|
|
||||||
# include <sys/time.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <chrono>
|
#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};
|
// const size_t kAV1NumberSubsamples[] = { 72, 144, 288, 576};
|
||||||
// clang-format on
|
// 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
|
} // namespace
|
||||||
|
|
||||||
class OEMCryptoClientTest : public ::testing::Test, public SessionUtil {
|
class OEMCryptoClientTest : public ::testing::Test, public SessionUtil {
|
||||||
@@ -6215,44 +6178,12 @@ class OEMCryptoUsageTableTestWallClock : public OEMCryptoUsageTableTest {
|
|||||||
public:
|
public:
|
||||||
void SetUp() override {
|
void SetUp() override {
|
||||||
OEMCryptoUsageTableTest::SetUp();
|
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 {
|
void TearDown() override {
|
||||||
if (did_change_system_time_) {
|
wvcdm::TestSleep::ResetRollback();
|
||||||
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));
|
|
||||||
}
|
|
||||||
OEMCryptoUsageTableTest::TearDown();
|
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
|
// 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
|
// 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
|
// the time to the last decrypt call since this requires hardware-secure clocks
|
||||||
// to guarantee.
|
// 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) {
|
TEST_P(OEMCryptoUsageTableTestWallClock, TimeRollbackPrevention) {
|
||||||
cout << "This test temporarily rolls back the system time in order to verify "
|
cout << "This test temporarily rolls back the system time in order to verify "
|
||||||
<< "that the usage report accounts for the change. It then rolls "
|
<< "that the usage report accounts for the change. After the test, it "
|
||||||
<< "the time back forward to the absolute time." << endl;
|
<< "rolls the clock back forward." << endl;
|
||||||
LicenseWithUsageEntry entry;
|
constexpr int kRollBackTime = kShortSleep * 2;
|
||||||
entry.MakeOfflineAndClose(this);
|
constexpr int kPlaybackCount = 3;
|
||||||
Session& s = entry.session();
|
constexpr int kTotalTime = kShortSleep * 8;
|
||||||
std::chrono::system_clock wall_clock;
|
|
||||||
std::chrono::steady_clock monotonic_clock;
|
|
||||||
const auto loaded = wall_clock.now();
|
|
||||||
|
|
||||||
ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this));
|
LicenseWithUsageEntry entry1;
|
||||||
const auto first_decrypt = wall_clock.now();
|
entry1.license_messages()
|
||||||
// Monotonic clock can't be changed. We use this since system clock will be
|
.core_response()
|
||||||
// unreliable.
|
.timer_limits.total_playback_duration_seconds = 7 * kShortSleep;
|
||||||
const auto first_decrypt_monotonic = monotonic_clock.now();
|
entry1.MakeOfflineAndClose(this);
|
||||||
ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR());
|
Session& s1 = entry1.session();
|
||||||
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
ASSERT_NO_FATAL_FAILURE(entry1.OpenAndReload(this));
|
||||||
ASSERT_NO_FATAL_FAILURE(s.close());
|
|
||||||
|
|
||||||
// Imitate playback.
|
LicenseWithUsageEntry entry2;
|
||||||
wvcdm::TestSleep::Sleep(kLongDuration * 2);
|
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));
|
// Start with three short intervals of playback for entry1.
|
||||||
ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR());
|
for (int i = 0; i < kPlaybackCount; i++) {
|
||||||
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
ASSERT_NO_FATAL_FAILURE(entry1.TestDecryptCTR());
|
||||||
ASSERT_NO_FATAL_FAILURE(s.close());
|
wvcdm::TestSleep::Sleep(kShortSleep);
|
||||||
|
ASSERT_NO_FATAL_FAILURE(entry1.TestDecryptCTR());
|
||||||
|
}
|
||||||
|
|
||||||
// Rollback the wall clock time.
|
|
||||||
cout << "Rolling the system time back..." << endl;
|
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(
|
ASSERT_NO_FATAL_FAILURE(
|
||||||
SetWallTimeDelta(-static_cast<int64_t>(kLongDuration) * 10));
|
entry1.TestDecryptCTR(false, OEMCrypto_ERROR_KEY_EXPIRED));
|
||||||
|
// Should not be able to start playback in entry2.
|
||||||
// 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;
|
|
||||||
ASSERT_NO_FATAL_FAILURE(
|
ASSERT_NO_FATAL_FAILURE(
|
||||||
SetWallTimeDelta(test_duration / std::chrono::seconds(1)));
|
entry2.TestDecryptCTR(true, OEMCrypto_ERROR_KEY_EXPIRED));
|
||||||
// Need to update time created since the verification checks the time of PST
|
|
||||||
// report creation.
|
|
||||||
expected.time_created = UnixTime(wall_clock.now());
|
|
||||||
|
|
||||||
const auto end_time = first_decrypt + test_duration;
|
// Now we look at the usage reports:
|
||||||
ASSERT_NO_FATAL_FAILURE(s.VerifyReport(
|
ASSERT_NO_FATAL_FAILURE(s1.UpdateUsageEntry(&encrypted_usage_header_));
|
||||||
expected, UnixTime(loaded), UnixTime(first_decrypt), UnixTime(end_time)));
|
ASSERT_NO_FATAL_FAILURE(s2.UpdateUsageEntry(&encrypted_usage_header_));
|
||||||
ASSERT_NO_FATAL_FAILURE(s.close());
|
|
||||||
|
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.
|
// Verify that a large PST can be used with usage table entries.
|
||||||
|
|||||||
Reference in New Issue
Block a user