OEMCrypto v16.3

There were no new changes to the OEMCrypto code. However, the ODK
library changed, so we rolled the minor version number to 3.  The ODK
library was updated to support a nonce-free offline license. An
offline license would not require a nonce if, for example, it is
preloaded onto the device and does not have an entry in the usage
table.

Also, the following unit tests have been updated:
1. Various tests: Keys are not derived if they are not used. This is more
   in line with the “OEMCrypto state” diagram below.
2. The decrypt hash is not verified when there are multiple samples or no
   key is selected.
3. LoadKeyWithNoRequest. A nonce-free license is loaded in a session that
   did not sign the request. (Requires 16.3 ODK library)
4. RefreshLargeBuffer. The renewal message was set to the large
   size. Previously, only the license request was set to the larger size.
5. OEMCryptoGenericCryptoTest.*LargeBuffer. The correct buffer size is
   now being used.
6. ShrinkOverOpenSessions: The correct error code
   OEMCrypto_ERROR_ENTRY_IN_USE is now verified.
7. TimeRollbackPrevention: The test was refactored and fixed. Comments
   were added.
This commit is contained in:
Fred Gylys-Colwell
2020-06-02 10:14:16 -07:00
parent e000b9de99
commit b5637c89cb
26 changed files with 373 additions and 294 deletions

View File

@@ -8,7 +8,7 @@
* Reference APIs needed to support Widevine's crypto algorithms.
*
* See the document "WV Modular DRM Security Integration Guide for Common
* Encryption (CENC) -- version 16.2" for a description of this API. You
* Encryption (CENC) -- version 16.3" for a description of this API. You
* can find this document in the widevine repository as
* docs/WidevineModularDRMSecurityIntegrationGuideforCENC_v16.pdf
* Changes between different versions of this API are documented in the files

View File

@@ -1,4 +1,4 @@
The ODK Library is used to generate and parse core OEMCrypto messages for
This ODK Library is used to generate and parse core OEMCrypto messages for
OEMCrypto v16 and above.
This library is used by both OEMCrypto on a device, and by Widevine license and

View File

@@ -12,7 +12,13 @@
/* The version of this library. */
#define ODK_MAJOR_VERSION 16
#define ODK_MINOR_VERSION 2
#define ODK_MINOR_VERSION 3
/* ODK Version string. Date changed automatically on each release. */
#define ODK_RELEASE_DATE "ODK v16.2 2020-06-02"
/* The lowest version number for an ODK message. */
#define ODK_FIRST_VERSION 16
/* Some useful constants. */
#define ODK_DEVICE_ID_LEN_MAX 64

View File

@@ -19,9 +19,6 @@ namespace oemcrypto_core_message {
namespace deserialize {
namespace {
constexpr int EARLIEST_OEMCRYPTO_VERSION_WITH_ODK = 16;
constexpr int LATEST_OEMCRYPTO_VERSION = 16;
/**
* Template for parsing requests
*
@@ -59,21 +56,31 @@ bool ParseRequest(uint32_t message_type,
core_request->session_id = core_message.nonce_values.session_id;
// Verify that the minor version matches the released version for the given
// major version.
if ((core_request->api_major_version < EARLIEST_OEMCRYPTO_VERSION_WITH_ODK) ||
(core_request->api_major_version > LATEST_OEMCRYPTO_VERSION)) {
// Non existing and future versions are not supported.
if (core_request->api_major_version < ODK_FIRST_VERSION) {
// Non existing versions are not supported.
return false;
} else if (core_request->api_major_version == 16) {
// For version 16, we demand a minor version of at least 2.
// We accept 16.2, 16.3, or higher.
if (core_request->api_major_version < 2) return false;
} else {
// Other versions do not (yet) have a restriction on minor number.
// In particular, future versions are accepted for forward compatibility.
}
return core_message.message_type == message_type &&
core_message.message_length == GetOffset(msg) &&
core_request->api_major_version >=
EARLIEST_OEMCRYPTO_VERSION_WITH_ODK &&
core_request->api_major_version <= LATEST_OEMCRYPTO_VERSION;
// For v16, a release and a renewal use the same message structure.
// However, for future API versions, the release might be a separate
// message. Otherwise, we expect an exact match of message types.
if (core_message.message_type != message_type &&
!(message_type == ODK_Renewal_Request_Type &&
core_message.message_type == ODK_Release_Request_Type)) {
return false;
}
// Verify that the amount of buffer we read, which is GetOffset, is not more
// than the total message size. We allow the total message size to be larger
// for forward compatibility because future messages might have extra fields
// that we can ignore.
if (core_message.message_length < GetOffset(msg)) return false;
return true;
}
} // namespace

View File

@@ -41,6 +41,12 @@ bool CreateResponse(uint32_t message_type, const S& core_request,
header->nonce_values.api_minor_version = core_request.api_minor_version;
header->nonce_values.nonce = core_request.nonce;
header->nonce_values.session_id = core_request.session_id;
// The message API version for the response is the minimum of our version and
// the request's version.
if (core_request.api_major_version > ODK_MAJOR_VERSION) {
header->nonce_values.api_major_version = ODK_MAJOR_VERSION;
header->nonce_values.api_minor_version = ODK_MINOR_VERSION;
}
static constexpr size_t BUF_CAPACITY = 2048;
std::vector<uint8_t> buf(BUF_CAPACITY, 0);

View File

@@ -87,8 +87,7 @@ bool CreateCoreLicenseResponseFromProto(const std::string& serialized_license,
}
parsed_lic.enc_mac_keys_iv =
GetOecSubstring(serialized_license, k.iv());
std::string mac_keys(k.key(), k.key().size());
parsed_lic.enc_mac_keys = GetOecSubstring(serialized_license, mac_keys);
parsed_lic.enc_mac_keys = GetOecSubstring(serialized_license, k.key());
break;
}
case video_widevine::License_KeyContainer::CONTENT: {

View File

@@ -190,9 +190,9 @@ OEMCryptoResult ODK_PrepareCoreRenewalRequest(uint8_t* message,
* renewal. All releases use v15. */
if (clock_values->timer_status == ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED ||
clock_values->timer_status == ODK_CLOCK_TIMER_STATUS_LICENSE_INACTIVE) {
nonce_values->api_major_version = 15;
nonce_values->api_major_version = ODK_FIRST_VERSION - 1;
}
if (nonce_values->api_major_version < 16) {
if (nonce_values->api_major_version < ODK_FIRST_VERSION) {
*core_message_size = 0;
return OEMCrypto_SUCCESS;
}
@@ -270,12 +270,28 @@ OEMCryptoResult ODK_ParseLicense(
return err;
}
/* This function should not be used for legacy licenses. */
if (license_response.request.core_message.nonce_values.api_major_version !=
ODK_MAJOR_VERSION) {
/* We do not support future API version. Also, this function should not be
* used for legacy licenses. */
if (license_response.request.core_message.nonce_values.api_major_version >
ODK_MAJOR_VERSION ||
license_response.request.core_message.nonce_values.api_major_version <
ODK_FIRST_VERSION) {
return ODK_UNSUPPORTED_API;
}
/* If the server sent us an older format, record the license's API version. */
if (nonce_values->api_major_version >
license_response.request.core_message.nonce_values.api_major_version) {
nonce_values->api_major_version =
license_response.request.core_message.nonce_values.api_major_version;
nonce_values->api_minor_version =
license_response.request.core_message.nonce_values.api_minor_version;
} else if (nonce_values->api_minor_version >
license_response.request.core_message.nonce_values
.api_minor_version) {
nonce_values->api_minor_version =
license_response.request.core_message.nonce_values.api_minor_version;
}
/* If the license has a provider session token (pst), then OEMCrypto should
* have a usage entry loaded. The opposite is also an error. */
if ((usage_entry_present && parsed_license->pst.length == 0) ||
@@ -302,7 +318,7 @@ OEMCryptoResult ODK_ParseLicense(
* OEMCrypto stores a hash of the core license request and only signs the
* message body. Here, when we process the license response, we verify that
* the server has the same hash of the core request. */
if (initial_license_load &&
if (initial_license_load && parsed_license->nonce_required &&
crypto_memcmp(request_hash, license_response.request_hash,
ODK_SHA256_HASH_SIZE)) {
return ODK_ERROR_CORE_MESSAGE;

View File

@@ -10,11 +10,11 @@ extern "C" {
#endif
#if (__STDC_VERSION__ >= 201112L)
# include <assert.h>
# define odk_static_assert static_assert
#include <assert.h>
#define odk_static_assert static_assert
#else
# define odk_static_assert(msg, e) \
enum { odk_static_assert = 1 / (!!((msg) && (e))) };
#define odk_static_assert(msg, e) \
enum { odk_static_assert = 1 / (!!((msg) && (e))) };
#endif
#ifdef __cplusplus

View File

@@ -10,11 +10,11 @@ extern "C" {
#endif
#if defined(__linux__) || defined(__ANDROID__)
# include <endian.h>
# define oemcrypto_htobe32 htobe32
# define oemcrypto_be32toh be32toh
# define oemcrypto_htobe64 htobe64
# define oemcrypto_be64toh be64toh
#include <endian.h>
#define oemcrypto_htobe32 htobe32
#define oemcrypto_be32toh be32toh
#define oemcrypto_htobe64 htobe64
#define oemcrypto_be64toh be64toh
#else /* defined(__linux__) || defined(__ANDROID__) */
uint32_t oemcrypto_htobe32(uint32_t u32);
uint32_t oemcrypto_be32toh(uint32_t u32);

View File

@@ -12,20 +12,9 @@
extern "C" {
#endif
#ifndef __has_builtin
# define __has_builtin(x) 0
#endif
#if (defined(__GNUC__) && __GNUC__ >= 5) || \
__has_builtin(__builtin_add_overflow)
# define odk_sub_overflow_u64 __builtin_sub_overflow
# define odk_add_overflow_u64 __builtin_add_overflow
# define odk_add_overflow_ux __builtin_add_overflow
#else
int odk_sub_overflow_u64(uint64_t a, uint64_t b, uint64_t* c);
int odk_add_overflow_u64(uint64_t a, uint64_t b, uint64_t* c);
int odk_add_overflow_ux(size_t a, size_t b, size_t* c);
#endif
#ifdef __cplusplus
}

View File

@@ -151,7 +151,7 @@ static void Unpack_ODK_ParsedLicense(Message* msg, ODK_ParsedLicense* obj) {
Unpack_OEMCrypto_Substring(msg, &obj->enc_mac_keys);
Unpack_OEMCrypto_Substring(msg, &obj->pst);
Unpack_OEMCrypto_Substring(msg, &obj->srm_restriction_data);
obj->license_type = Unpack_enum(msg);
obj->license_type = (OEMCrypto_LicenseType)Unpack_enum(msg);
Unpack_bool(msg, &obj->nonce_required);
Unpack_ODK_TimerLimits(msg, &obj->timer_limits);
Unpack_uint32_t(msg, &obj->key_array_length);
@@ -167,7 +167,7 @@ static void Unpack_ODK_ParsedLicense(Message* msg, ODK_ParsedLicense* obj) {
static void Unpack_ODK_ParsedProvisioning(Message* msg,
ODK_ParsedProvisioning* obj) {
obj->key_type = Unpack_enum(msg);
obj->key_type = (OEMCrypto_PrivateKeyType)Unpack_enum(msg);
Unpack_OEMCrypto_Substring(msg, &obj->enc_private_key);
Unpack_OEMCrypto_Substring(msg, &obj->enc_private_key_iv);
Unpack_OEMCrypto_Substring(msg, &obj->encrypted_message_key);

View File

@@ -21,6 +21,10 @@ typedef enum {
ODK_Renewal_Response_Type = 4,
ODK_Provisioning_Request_Type = 5,
ODK_Provisioning_Response_Type = 6,
/* Reserve future message types to support forward compatibility. */
ODK_Release_Request_Type = 7,
ODK_Release_Response_Type = 8,
} ODK_MessageType;
typedef struct {

View File

@@ -4,6 +4,7 @@
#include <stdint.h>
#include <string.h>
#include "odk.h"
#include "odk_overflow.h"
#include "odk_structs_priv.h"

View File

@@ -14,8 +14,8 @@ int crypto_memcmp(const void* in_a, const void* in_b, size_t len) {
return -1;
}
const uint8_t* a = in_a;
const uint8_t* b = in_b;
const uint8_t* a = (const uint8_t*)in_a;
const uint8_t* b = (const uint8_t*)in_b;
uint8_t x = 0;
for (size_t i = 0; i < len; i++) {

View File

@@ -102,6 +102,7 @@ int64_t CryptoEngine::MonotonicTime() {
wvcdm::Clock().GetCurrentTime() + offline_time_info_.rollback_offset;
static int64_t then = now;
if (now < then) {
LOGW("Clock rollback detected: %lld seconds", then - now);
offline_time_info_.rollback_offset += then - now;
now = then;
}
@@ -138,31 +139,16 @@ bool CryptoEngine::LoadOfflineTimeInfo(const std::string& file_path) {
memset(&offline_time_info_, 0, sizeof(TimeInfo));
wvcdm::FileSystem* file_system = file_system_.get();
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 =
file_system->Open(file_path, wvcdm::FileSystem::kReadOnly);
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;
}
// Load time info from previous call.
file->Read(reinterpret_cast<char*>(&encrypted_buffer[0]), 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));
file->Read(reinterpret_cast<char*>(&offline_time_info_), sizeof(TimeInfo));
// Detect offline time rollback after loading from disk.
// 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)
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;
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_path, wvcdm::FileSystem::kCreate | wvcdm::FileSystem::kTruncate);
if (!file) {
LOGE("File open failed: %s", file_path.c_str());
return false;
}
file->Write(reinterpret_cast<char*>(&encrypted_buffer[0]), sizeof(TimeInfo));
file->Write(reinterpret_cast<char*>(&offline_time_info_), sizeof(TimeInfo));
return true;
}

View File

@@ -11,17 +11,9 @@
namespace wvoec_ref {
SessionKeyTable::~SessionKeyTable() {
for (KeyMap::iterator i = keys_.begin(); i != keys_.end(); ++i) {
if (nullptr != i->second) {
delete i->second;
}
}
}
bool SessionKeyTable::Insert(const KeyId key_id, const Key& key_data) {
if (keys_.find(key_id) != keys_.end()) return false;
keys_[key_id] = new Key(key_data);
keys_[key_id] = std::unique_ptr<Key>(new Key(key_data));
return true;
}
@@ -29,12 +21,11 @@ Key* SessionKeyTable::Find(const KeyId key_id) {
if (keys_.find(key_id) == keys_.end()) {
return nullptr;
}
return keys_[key_id];
return keys_[key_id].get();
}
void SessionKeyTable::Remove(const KeyId key_id) {
if (keys_.find(key_id) != keys_.end()) {
delete keys_[key_id];
keys_.erase(key_id);
}
}
@@ -49,7 +40,7 @@ bool EntitlementKeyTable::Insert(const KeyId key_id, const Key& key_data) {
// |key_id| and |key_data| are for an entitlement key. Insert a new
// entitlement key entry.
if (keys_.find(key_id) != keys_.end()) return false;
keys_[key_id] = new EntitlementKey(key_data);
keys_[key_id] = std::unique_ptr<EntitlementKey>(new EntitlementKey(key_data));
// If this is a new insertion, we don't have a content key assigned yet.
return true;
}
@@ -65,7 +56,7 @@ Key* EntitlementKeyTable::Find(const KeyId key_id) {
if (keys_.find(it->second) == keys_.end()) {
return nullptr;
}
return keys_[it->second];
return keys_[it->second].get();
}
void EntitlementKeyTable::Remove(const KeyId key_id) {
@@ -108,7 +99,7 @@ EntitlementKey* EntitlementKeyTable::GetEntitlementKey(
if (it == keys_.end()) {
return nullptr;
}
return it->second;
return it->second.get();
}
} // namespace wvoec_ref

View File

@@ -9,6 +9,7 @@
#include <stdint.h>
#include <map>
#include <memory>
#include <vector>
#include "disallow_copy_and_assign.h"
@@ -23,18 +24,18 @@ class UsageTable;
class UsageTableEntry;
typedef std::vector<uint8_t> KeyId;
typedef std::map<KeyId, Key*> KeyMap;
typedef std::map<KeyId, EntitlementKey*> EntitlementKeyMap;
typedef std::map<KeyId, std::unique_ptr<Key>> KeyMap;
typedef std::map<KeyId, std::unique_ptr<EntitlementKey>> EntitlementKeyMap;
// SessionKeyTable holds the keys for the current session
class SessionKeyTable {
public:
SessionKeyTable() {}
~SessionKeyTable();
~SessionKeyTable() {}
bool Insert(const KeyId key_id, const Key& key_data);
Key* Find(const KeyId key_id);
Key* FirstKey() { return keys_.begin()->second; }
Key* FirstKey() { return keys_.begin()->second.get(); }
void Remove(const KeyId key_id);
void UpdateDuration(const KeyControlBlock& control);
size_t size() const { return keys_.size(); }
@@ -53,7 +54,7 @@ class EntitlementKeyTable {
~EntitlementKeyTable() {}
bool Insert(const KeyId key_id, const Key& key_data);
Key* Find(const KeyId key_id);
Key* FirstKey() { return keys_.begin()->second; }
Key* FirstKey() { return keys_.begin()->second.get(); }
void Remove(const KeyId key_id);
void UpdateDuration(const KeyControlBlock& control);
size_t size() const { return contentid_to_entitlementid_.size(); }

View File

@@ -8,58 +8,15 @@
#include <stdio.h>
#ifdef _WIN32
# include <windows.h>
#else
# include <sys/types.h>
# include <unistd.h>
#endif
#include <cstring>
#include "oec_test_data.h"
#include "test_sleep.h"
namespace wvoec {
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() {
if (initialized_) return;
uses_keybox = false;
@@ -187,8 +144,11 @@ std::string DeviceFeatures::RestrictFilter(const std::string& initial_filter) {
// clang-format on
// Some tests may require root access. If user is not root, filter these tests
// out.
if (!CanChangeTime()) {
if (!wvcdm::TestSleep::CanChangeSystemTime()) {
printf("Filtering out TimeRollbackPrevention.\n");
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
// specifically requested.

View File

@@ -635,7 +635,7 @@ OEMCryptoResult LicenseRoundTrip::LoadResponse(Session* session) {
// Note: we verify content licenses here. For entitlement license, we verify
// the key control blocks after loading entitled content keys.
if (license_type_ == OEMCrypto_ContentLicense) VerifyTestKeys();
if (license_type_ == OEMCrypto_ContentLicense) VerifyTestKeys(session);
}
return result;
}
@@ -649,12 +649,12 @@ OEMCryptoResult LicenseRoundTrip::ReloadResponse(Session* session) {
// with the truth key control block. Failures in this function probably
// indicate the OEMCrypto_LoadLicense/LoadKeys did not correctly process the key
// control block.
void LicenseRoundTrip::VerifyTestKeys() {
void LicenseRoundTrip::VerifyTestKeys(Session* session) {
for (unsigned int i = 0; i < num_keys_; i++) {
KeyControlBlock block;
size_t size = sizeof(block);
OEMCryptoResult sts = OEMCrypto_QueryKeyControl(
session_->session_id(), response_data_.keys[i].key_id,
session->session_id(), response_data_.keys[i].key_id,
response_data_.keys[i].key_id_length,
reinterpret_cast<uint8_t*>(&block), &size);
if (sts != OEMCrypto_ERROR_NOT_IMPLEMENTED) {
@@ -1014,7 +1014,7 @@ void Session::GenerateDerivedKeysFromSessionKey() {
// Uses test certificate.
vector<uint8_t> session_key;
vector<uint8_t> enc_session_key;
if (public_rsa_ == nullptr) PreparePublicKey();
ASSERT_NE(public_rsa_, nullptr) << "No public RSA key loaded in test code.";
// A failure here probably indicates that there is something wrong with the
// test program and its dependency on BoringSSL.
ASSERT_TRUE(GenerateRSASessionKey(&session_key, &enc_session_key));
@@ -1303,12 +1303,11 @@ void Session::VerifyRSASignature(const vector<uint8_t>& message,
const uint8_t* signature,
size_t signature_length,
RSA_Padding_Scheme padding_scheme) {
EXPECT_TRUE(nullptr != public_rsa_)
<< "No public RSA key loaded in test code.\n";
ASSERT_NE(public_rsa_, nullptr) << "No public RSA key loaded in test code.";
EXPECT_EQ(static_cast<size_t>(RSA_size(public_rsa_)), signature_length)
ASSERT_EQ(static_cast<size_t>(RSA_size(public_rsa_)), signature_length)
<< "Signature size is wrong. " << signature_length << ", should be "
<< RSA_size(public_rsa_) << "\n";
<< RSA_size(public_rsa_);
if (padding_scheme == kSign_RSASSA_PSS) {
boringssl_ptr<EVP_PKEY, EVP_PKEY_free> pkey(EVP_PKEY_new());
@@ -1477,7 +1476,10 @@ void Session::VerifyReport(Test_PST_Report expected,
int64_t time_first_decrypt,
int64_t time_last_decrypt) {
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 =
(time_first_decrypt > 0 && time_first_decrypt < now)
? now - time_first_decrypt

View File

@@ -298,7 +298,7 @@ class LicenseRoundTrip
// Reload an offline license into a different session. This derives new mac
// keys and then calls LoadResponse.
OEMCryptoResult ReloadResponse(Session* session);
void VerifyTestKeys();
void VerifyTestKeys(Session* session);
// Set the default key control block for all keys. This is used in
// CreateDefaultResponse. The key control block determines the restrictions
// that OEMCrypto should place on a key's use. For example, it specifies the

View File

@@ -81,6 +81,6 @@ void SessionUtil::InstallTestRSAKey(Session* s) {
ASSERT_NO_FATAL_FAILURE(s->InstallRSASessionTestKey(wrapped_rsa_key_));
}
// Test RSA key should be loaded.
ASSERT_NO_FATAL_FAILURE(s->GenerateDerivedKeysFromSessionKey());
ASSERT_NO_FATAL_FAILURE(s->PreparePublicKey());
}
} // namespace wvoec

View File

@@ -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 {
@@ -191,13 +154,13 @@ class OEMCryptoClientTest : public ::testing::Test, public SessionUtil {
// tests are failing when the device has the wrong keybox installed.
TEST_F(OEMCryptoClientTest, VersionNumber) {
const std::string log_message =
"OEMCrypto unit tests for API 16.2. Tests last updated 2020-03-27";
"OEMCrypto unit tests for API 16.3. Tests last updated 2020-06-01";
cout << " " << log_message << "\n";
LOGI("%s", log_message.c_str());
// If any of the following fail, then it is time to update the log message
// above.
EXPECT_EQ(ODK_MAJOR_VERSION, 16);
EXPECT_EQ(ODK_MINOR_VERSION, 2);
EXPECT_EQ(ODK_MINOR_VERSION, 3);
EXPECT_EQ(kCurrentAPI, 16u);
const char* level = OEMCrypto_SecurityLevel();
ASSERT_NE(nullptr, level);
@@ -725,6 +688,7 @@ TEST_F(OEMCryptoProv30Test, GetCertOnlyAPI16) {
ASSERT_NO_FATAL_FAILURE(s.open());
// Install the DRM Cert's RSA key.
ASSERT_NO_FATAL_FAILURE(s.InstallRSASessionTestKey(wrapped_rsa_key_));
ASSERT_NO_FATAL_FAILURE(s.PreparePublicKey());
// Request the OEM Cert. -- This should NOT load the OEM Private key.
vector<uint8_t> public_cert;
size_t public_cert_length = 0;
@@ -890,7 +854,13 @@ TEST_P(OEMCryptoLicenseTest, LoadKeyWithNoRequest) {
license_messages_.core_request().api_minor_version = ODK_MINOR_VERSION;
ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse());
ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse());
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse());
// Load license in a different session, which did not create the request.
Session session2;
ASSERT_NO_FATAL_FAILURE(session2.open());
ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&session2));
ASSERT_NO_FATAL_FAILURE(session2.GenerateDerivedKeysFromSessionKey());
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse(&session2));
}
// Verify that a license may be loaded with a nonce.
@@ -1865,7 +1835,7 @@ TEST_P(OEMCryptoRefreshTest, RefreshLargeBuffer) {
LoadLicense();
RenewalRoundTrip renewal_messages(&license_messages_);
const size_t max_size = GetResourceValue(kLargeMessageSize);
license_messages_.set_message_size(max_size);
renewal_messages.set_message_size(max_size);
MakeRenewalRequest(&renewal_messages);
LoadRenewal(&renewal_messages, OEMCrypto_SUCCESS);
}
@@ -2233,10 +2203,11 @@ class OEMCryptoSessionTestsDecryptTests
void TestDecryptCENC() {
OEMCryptoResult sts;
// OEMCrypto only supports providing a decrypt hash for one sample.
if (samples_.size() > 1) verify_crc_ = false;
// If supported, check the decrypt hashes.
if (verify_crc_) {
// OEMCrypto only supports providing a decrypt hash for the first sample
// in the sample array.
const TestSample& sample = samples_[0];
uint32_t hash =
@@ -2291,7 +2262,7 @@ class OEMCryptoSessionTestsDecryptTests
}
}
}
if (global_features.supports_crc) {
if (verify_crc_) {
uint32_t frame;
ASSERT_EQ(OEMCrypto_GetHashErrorCode(session_.session_id(), &frame),
OEMCrypto_SUCCESS);
@@ -4201,6 +4172,11 @@ class OEMCryptoGenericCryptoTest : public OEMCryptoRefreshTest {
}
}
void ResizeBuffer(size_t new_size) {
buffer_size_ = new_size;
InitializeClearBuffer(); // Re-initialize the clear buffer.
}
void EncryptAndLoadKeys() {
ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse());
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse());
@@ -4536,7 +4512,7 @@ TEST_P(OEMCryptoGenericCryptoTest, GenericKeyBadVerify) {
// Test Generic_Encrypt with the maximum buffer size.
TEST_P(OEMCryptoGenericCryptoTest, GenericKeyEncryptLargeBuffer) {
buffer_size_ = GetResourceValue(kMaxGenericBuffer);
ResizeBuffer(GetResourceValue(kMaxGenericBuffer));
EncryptAndLoadKeys();
unsigned int key_index = 0;
vector<uint8_t> expected_encrypted;
@@ -4559,7 +4535,7 @@ TEST_P(OEMCryptoGenericCryptoTest, GenericKeyEncryptLargeBuffer) {
// Test Generic_Decrypt with the maximum buffer size.
TEST_P(OEMCryptoGenericCryptoTest, GenericKeyDecryptLargeBuffer) {
// Some applications are known to pass in a block that is almost 400k.
buffer_size_ = GetResourceValue(kMaxGenericBuffer);
ResizeBuffer(GetResourceValue(kMaxGenericBuffer));
EncryptAndLoadKeys();
unsigned int key_index = 1;
vector<uint8_t> encrypted;
@@ -4580,7 +4556,7 @@ TEST_P(OEMCryptoGenericCryptoTest, GenericKeyDecryptLargeBuffer) {
// Test Generic_Sign with the maximum buffer size.
TEST_P(OEMCryptoGenericCryptoTest, GenericKeySignLargeBuffer) {
buffer_size_ = GetResourceValue(kMaxGenericBuffer);
ResizeBuffer(GetResourceValue(kMaxGenericBuffer));
EncryptAndLoadKeys();
unsigned int key_index = 2;
vector<uint8_t> expected_signature;
@@ -4608,7 +4584,7 @@ TEST_P(OEMCryptoGenericCryptoTest, GenericKeySignLargeBuffer) {
// Test Generic_Verify with the maximum buffer size.
TEST_P(OEMCryptoGenericCryptoTest, GenericKeyVerifyLargeBuffer) {
buffer_size_ = GetResourceValue(kMaxGenericBuffer);
ResizeBuffer(GetResourceValue(kMaxGenericBuffer));
EncryptAndLoadKeys();
unsigned int key_index = 3;
vector<uint8_t> signature;
@@ -4917,6 +4893,7 @@ class LicenseWithUsageEntry {
ASSERT_NO_FATAL_FAILURE(session_.open());
ASSERT_NO_FATAL_FAILURE(session_.ReloadUsageEntry());
ASSERT_NO_FATAL_FAILURE(util->InstallTestRSAKey(&session_));
ASSERT_NO_FATAL_FAILURE(session_.GenerateDerivedKeysFromSessionKey());
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse());
}
@@ -5719,19 +5696,29 @@ class OEMCryptoUsageTableDefragTest : public OEMCryptoUsageTableTest {
void ShrinkHeader(uint32_t new_size,
OEMCryptoResult expected_result = OEMCrypto_SUCCESS) {
// We call OEMCrypto_ShrinkUsageTableHeader once with a zero length buffer,
// so that OEMCrypto can tell us how big the buffer should be.
size_t header_buffer_length = 0;
OEMCryptoResult sts = OEMCrypto_ShrinkUsageTableHeader(
new_size, nullptr, &header_buffer_length);
// If we are expecting success, then the first call shall return
// SHORT_BUFFER. However, if we are not expecting success, this first call
// may return either SHORT_BUFFER or the expect error.
if (expected_result == OEMCrypto_SUCCESS) {
ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts);
} else {
ASSERT_NE(OEMCrypto_SUCCESS, sts);
if (sts != OEMCrypto_ERROR_SHORT_BUFFER) return;
} else if (sts != OEMCrypto_ERROR_SHORT_BUFFER) {
// If we got any thing from the first call, it should be the expected
// error, and we don't need to call a second time.
ASSERT_EQ(expected_result, sts);
return;
}
// If the first call resulted in SHORT_BUFFER, we should resize the buffer
// and try again.
ASSERT_LT(0u, header_buffer_length);
encrypted_usage_header_.resize(header_buffer_length);
sts = OEMCrypto_ShrinkUsageTableHeader(
new_size, encrypted_usage_header_.data(), &header_buffer_length);
// For the second call, we always demand the expected result.
ASSERT_EQ(expected_result, sts);
}
};
@@ -6030,6 +6017,7 @@ TEST_P(OEMCryptoUsageTableTest, ReloadUsageTableWithSkew) {
old_usage_entry_1.data(),
old_usage_entry_1.size()));
ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&s));
ASSERT_NO_FATAL_FAILURE(s.GenerateDerivedKeysFromSessionKey());
ASSERT_EQ(OEMCrypto_SUCCESS, entry.license_messages().LoadResponse());
}
@@ -6200,44 +6188,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
@@ -6246,61 +6202,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.

View File

@@ -4,16 +4,26 @@
#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 <chrono>
#include "clock.h"
#include "log.h"
namespace wvcdm {
bool TestSleep::real_sleep_ = true;
TestSleep::CallBack* TestSleep::callback_ = nullptr;
int TestSleep::total_clock_rollback_ = 0;
void TestSleep::Sleep(unsigned int 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
// calls. We sleep for approximately x second, and then advance the clock by
// 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;
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);
// We want to advance the fake clock by the difference between the real
// clock, and the previous value on the fake clock.
@@ -41,4 +51,98 @@ void TestSleep::SyncFakeClock() {
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

View File

@@ -22,8 +22,9 @@ class TestSleep {
virtual ~CallBack(){};
};
// If real_sleep_ is true, then this sleeps for |seconds| of time.
// If the callback exists, this calls the callback.
// If real_sleep_ is true, then this sleeps for |seconds| of time. If
// 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);
// 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.
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; }
private:
@@ -42,6 +65,9 @@ class TestSleep {
static bool real_sleep_;
// Called when the clock should advance.
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