diff --git a/docs/WidevineModularDRMSecurityIntegrationGuideforCENC_v16.pdf b/docs/WidevineModularDRMSecurityIntegrationGuideforCENC_v16.pdf index 15c1220..c94f32e 100644 Binary files a/docs/WidevineModularDRMSecurityIntegrationGuideforCENC_v16.pdf and b/docs/WidevineModularDRMSecurityIntegrationGuideforCENC_v16.pdf differ diff --git a/docs/Widevine_Modular_DRM_Version_16_Delta.pdf b/docs/Widevine_Modular_DRM_Version_16_Delta.pdf index 9a0aad1..babd737 100644 Binary files a/docs/Widevine_Modular_DRM_Version_16_Delta.pdf and b/docs/Widevine_Modular_DRM_Version_16_Delta.pdf differ diff --git a/oemcrypto/include/OEMCryptoCENC.h b/oemcrypto/include/OEMCryptoCENC.h index db54f9d..6e71a89 100644 --- a/oemcrypto/include/OEMCryptoCENC.h +++ b/oemcrypto/include/OEMCryptoCENC.h @@ -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 diff --git a/oemcrypto/odk/README b/oemcrypto/odk/README index 44dac83..ba8c8c7 100644 --- a/oemcrypto/odk/README +++ b/oemcrypto/odk/README @@ -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 diff --git a/oemcrypto/odk/include/odk_structs.h b/oemcrypto/odk/include/odk_structs.h index b24f7d9..d2cb635 100644 --- a/oemcrypto/odk/include/odk_structs.h +++ b/oemcrypto/odk/include/odk_structs.h @@ -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 diff --git a/oemcrypto/odk/src/core_message_deserialize.cpp b/oemcrypto/odk/src/core_message_deserialize.cpp index f2e909b..1cef5ee 100644 --- a/oemcrypto/odk/src/core_message_deserialize.cpp +++ b/oemcrypto/odk/src/core_message_deserialize.cpp @@ -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 diff --git a/oemcrypto/odk/src/core_message_serialize.cpp b/oemcrypto/odk/src/core_message_serialize.cpp index cdd9744..4546460 100644 --- a/oemcrypto/odk/src/core_message_serialize.cpp +++ b/oemcrypto/odk/src/core_message_serialize.cpp @@ -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 buf(BUF_CAPACITY, 0); diff --git a/oemcrypto/odk/src/core_message_serialize_proto.cpp b/oemcrypto/odk/src/core_message_serialize_proto.cpp index 90b5fdd..cacba42 100644 --- a/oemcrypto/odk/src/core_message_serialize_proto.cpp +++ b/oemcrypto/odk/src/core_message_serialize_proto.cpp @@ -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: { diff --git a/oemcrypto/odk/src/odk.c b/oemcrypto/odk/src/odk.c index be34f87..42e82d5 100644 --- a/oemcrypto/odk/src/odk.c +++ b/oemcrypto/odk/src/odk.c @@ -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; diff --git a/oemcrypto/odk/src/odk_assert.h b/oemcrypto/odk/src/odk_assert.h index 2057c40..6fda98b 100644 --- a/oemcrypto/odk/src/odk_assert.h +++ b/oemcrypto/odk/src/odk_assert.h @@ -10,11 +10,11 @@ extern "C" { #endif #if (__STDC_VERSION__ >= 201112L) -# include -# define odk_static_assert static_assert +#include +#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 diff --git a/oemcrypto/odk/src/odk_endian.h b/oemcrypto/odk/src/odk_endian.h index bfbb9bf..2a6f143 100644 --- a/oemcrypto/odk/src/odk_endian.h +++ b/oemcrypto/odk/src/odk_endian.h @@ -10,11 +10,11 @@ extern "C" { #endif #if defined(__linux__) || defined(__ANDROID__) -# include -# define oemcrypto_htobe32 htobe32 -# define oemcrypto_be32toh be32toh -# define oemcrypto_htobe64 htobe64 -# define oemcrypto_be64toh be64toh +#include +#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); diff --git a/oemcrypto/odk/src/odk_overflow.h b/oemcrypto/odk/src/odk_overflow.h index 3fcb32c..b1e03ee 100644 --- a/oemcrypto/odk/src/odk_overflow.h +++ b/oemcrypto/odk/src/odk_overflow.h @@ -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 } diff --git a/oemcrypto/odk/src/odk_serialize.c b/oemcrypto/odk/src/odk_serialize.c index e050050..efd9475 100644 --- a/oemcrypto/odk/src/odk_serialize.c +++ b/oemcrypto/odk/src/odk_serialize.c @@ -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); diff --git a/oemcrypto/odk/src/odk_structs_priv.h b/oemcrypto/odk/src/odk_structs_priv.h index 991f59d..3c5a502 100644 --- a/oemcrypto/odk/src/odk_structs_priv.h +++ b/oemcrypto/odk/src/odk_structs_priv.h @@ -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 { diff --git a/oemcrypto/odk/src/odk_timer.c b/oemcrypto/odk/src/odk_timer.c index 10a0f7d..0fbcf18 100644 --- a/oemcrypto/odk/src/odk_timer.c +++ b/oemcrypto/odk/src/odk_timer.c @@ -4,6 +4,7 @@ #include #include + #include "odk.h" #include "odk_overflow.h" #include "odk_structs_priv.h" diff --git a/oemcrypto/odk/src/odk_util.c b/oemcrypto/odk/src/odk_util.c index d102426..ff85a9c 100644 --- a/oemcrypto/odk/src/odk_util.c +++ b/oemcrypto/odk/src/odk_util.c @@ -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++) { diff --git a/oemcrypto/ref/src/oemcrypto_engine_ref.cpp b/oemcrypto/ref/src/oemcrypto_engine_ref.cpp index 72dfc85..f689739 100644 --- a/oemcrypto/ref/src/oemcrypto_engine_ref.cpp +++ b/oemcrypto/ref/src/oemcrypto_engine_ref.cpp @@ -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 encrypted_buffer(sizeof(TimeInfo)); - std::vector 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& key = DeviceRootKey(); std::unique_ptr 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(&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 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(&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& key = DeviceRootKey(); - std::vector encrypted_buffer(sizeof(TimeInfo)); - std::vector 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 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 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(&encrypted_buffer[0]), sizeof(TimeInfo)); + file->Write(reinterpret_cast(&offline_time_info_), sizeof(TimeInfo)); return true; } diff --git a/oemcrypto/ref/src/oemcrypto_session_key_table.cpp b/oemcrypto/ref/src/oemcrypto_session_key_table.cpp index e713197..00c16ed 100644 --- a/oemcrypto/ref/src/oemcrypto_session_key_table.cpp +++ b/oemcrypto/ref/src/oemcrypto_session_key_table.cpp @@ -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(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(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 diff --git a/oemcrypto/ref/src/oemcrypto_session_key_table.h b/oemcrypto/ref/src/oemcrypto_session_key_table.h index c84096b..ad74b87 100644 --- a/oemcrypto/ref/src/oemcrypto_session_key_table.h +++ b/oemcrypto/ref/src/oemcrypto_session_key_table.h @@ -9,6 +9,7 @@ #include #include +#include #include #include "disallow_copy_and_assign.h" @@ -23,18 +24,18 @@ class UsageTable; class UsageTableEntry; typedef std::vector KeyId; -typedef std::map KeyMap; -typedef std::map EntitlementKeyMap; +typedef std::map> KeyMap; +typedef std::map> 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(); } diff --git a/oemcrypto/test/oec_device_features.cpp b/oemcrypto/test/oec_device_features.cpp index bf9d61a..de1fcf4 100644 --- a/oemcrypto/test/oec_device_features.cpp +++ b/oemcrypto/test/oec_device_features.cpp @@ -8,58 +8,15 @@ #include -#ifdef _WIN32 -# include -#else -# include -# include -#endif - #include #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 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 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. diff --git a/oemcrypto/test/oec_session_util.cpp b/oemcrypto/test/oec_session_util.cpp index 662063f..26a98f5 100644 --- a/oemcrypto/test/oec_session_util.cpp +++ b/oemcrypto/test/oec_session_util.cpp @@ -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(&block), &size); if (sts != OEMCrypto_ERROR_NOT_IMPLEMENTED) { @@ -1014,7 +1014,7 @@ void Session::GenerateDerivedKeysFromSessionKey() { // Uses test certificate. vector session_key; vector 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& 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(RSA_size(public_rsa_)), signature_length) + ASSERT_EQ(static_cast(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 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 diff --git a/oemcrypto/test/oec_session_util.h b/oemcrypto/test/oec_session_util.h index 36848d5..54ee318 100644 --- a/oemcrypto/test/oec_session_util.h +++ b/oemcrypto/test/oec_session_util.h @@ -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 diff --git a/oemcrypto/test/oemcrypto_session_tests_helper.cpp b/oemcrypto/test/oemcrypto_session_tests_helper.cpp index 9e2bc7e..da8d5ec 100644 --- a/oemcrypto/test/oemcrypto_session_tests_helper.cpp +++ b/oemcrypto/test/oemcrypto_session_tests_helper.cpp @@ -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 diff --git a/oemcrypto/test/oemcrypto_test.cpp b/oemcrypto/test/oemcrypto_test.cpp index 06144d7..b04eba2 100644 --- a/oemcrypto/test/oemcrypto_test.cpp +++ b/oemcrypto/test/oemcrypto_test.cpp @@ -14,12 +14,6 @@ #include #include -#ifdef _WIN32 -# include -#else -# include -#endif - #include #include #include @@ -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 -uint64_t UnixTime( - const std::chrono::time_point& 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(file_time.dwLowDateTime) | - (static_cast(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 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 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 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 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 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 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(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. diff --git a/util/test/test_sleep.cpp b/util/test/test_sleep.cpp index 2140c9e..1656381 100644 --- a/util/test/test_sleep.cpp +++ b/util/test/test_sleep.cpp @@ -4,16 +4,26 @@ #include "test_sleep.h" +#ifdef _WIN32 +# include +#else +# include +#endif + +#include +#include #include #include #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(file_time.dwLowDateTime) | + (static_cast(file_time.dwHighDateTime) << 32); + long_time += static_cast(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( + modified_time.time_since_epoch()) + .count(); + time_spec.tv_nsec = std::chrono::duration_cast( + 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 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 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 diff --git a/util/test/test_sleep.h b/util/test/test_sleep.h index 832f1ad..34c4b3f 100644 --- a/util/test/test_sleep.h +++ b/util/test/test_sleep.h @@ -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