From 2443fe807afc94911bc7d02daf0f205d7d8cef46 Mon Sep 17 00:00:00 2001 From: Robert Shih Date: Thu, 12 Sep 2019 23:31:31 -0700 Subject: [PATCH] odk: core serialization structs & functions odk directory copied from wvgerrit. branch oemcrypto-v16 commit 0c9a7dc Bug: 140758896 Test: odk_test Change-Id: I0c631f771b794468a63e4395f6b9c3b60a1dfd4f --- libwvdrmengine/oemcrypto/odk/Android.bp | 56 ++ .../odk/include/OEMCryptoCENCCommon.h | 153 +++ libwvdrmengine/oemcrypto/odk/include/odk.h | 583 +++++++++++ .../oemcrypto/odk/include/odk_assert.h | 27 + .../oemcrypto/odk/include/odk_overflow.h | 33 + .../oemcrypto/odk/include/odk_serialize.h | 44 + .../oemcrypto/odk/include/odk_structs.h | 96 ++ .../oemcrypto/odk/include/odk_structs_priv.h | 54 + .../odk/include/serialization_base.h | 91 ++ .../oemcrypto/odk/kdo/include/oec_util.h | 172 ++++ .../odk/kdo/include/oec_util_proto.h | 59 ++ .../oemcrypto/odk/kdo/src/oec_util.cpp | 209 ++++ .../oemcrypto/odk/kdo/src/oec_util_proto.cpp | 161 +++ libwvdrmengine/oemcrypto/odk/src/odk.c | 316 ++++++ .../oemcrypto/odk/src/odk_overflow.c | 38 + .../oemcrypto/odk/src/odk_serialize.c | 209 ++++ libwvdrmengine/oemcrypto/odk/src/odk_timer.c | 229 +++++ .../oemcrypto/odk/src/serialization_base.c | 205 ++++ .../oemcrypto/odk/test/odk_fuzz.cpp | 223 +++++ .../oemcrypto/odk/test/odk_test.cpp | 684 +++++++++++++ libwvdrmengine/oemcrypto/odk/test/odk_test.h | 64 ++ .../oemcrypto/odk/test/odk_timer_test.cpp | 936 ++++++++++++++++++ 22 files changed, 4642 insertions(+) create mode 100644 libwvdrmengine/oemcrypto/odk/Android.bp create mode 100644 libwvdrmengine/oemcrypto/odk/include/OEMCryptoCENCCommon.h create mode 100644 libwvdrmengine/oemcrypto/odk/include/odk.h create mode 100644 libwvdrmengine/oemcrypto/odk/include/odk_assert.h create mode 100644 libwvdrmengine/oemcrypto/odk/include/odk_overflow.h create mode 100644 libwvdrmengine/oemcrypto/odk/include/odk_serialize.h create mode 100644 libwvdrmengine/oemcrypto/odk/include/odk_structs.h create mode 100644 libwvdrmengine/oemcrypto/odk/include/odk_structs_priv.h create mode 100644 libwvdrmengine/oemcrypto/odk/include/serialization_base.h create mode 100644 libwvdrmengine/oemcrypto/odk/kdo/include/oec_util.h create mode 100644 libwvdrmengine/oemcrypto/odk/kdo/include/oec_util_proto.h create mode 100644 libwvdrmengine/oemcrypto/odk/kdo/src/oec_util.cpp create mode 100644 libwvdrmengine/oemcrypto/odk/kdo/src/oec_util_proto.cpp create mode 100644 libwvdrmengine/oemcrypto/odk/src/odk.c create mode 100644 libwvdrmengine/oemcrypto/odk/src/odk_overflow.c create mode 100644 libwvdrmengine/oemcrypto/odk/src/odk_serialize.c create mode 100644 libwvdrmengine/oemcrypto/odk/src/odk_timer.c create mode 100644 libwvdrmengine/oemcrypto/odk/src/serialization_base.c create mode 100644 libwvdrmengine/oemcrypto/odk/test/odk_fuzz.cpp create mode 100644 libwvdrmengine/oemcrypto/odk/test/odk_test.cpp create mode 100644 libwvdrmengine/oemcrypto/odk/test/odk_test.h create mode 100644 libwvdrmengine/oemcrypto/odk/test/odk_timer_test.cpp diff --git a/libwvdrmengine/oemcrypto/odk/Android.bp b/libwvdrmengine/oemcrypto/odk/Android.bp new file mode 100644 index 00000000..d3a65382 --- /dev/null +++ b/libwvdrmengine/oemcrypto/odk/Android.bp @@ -0,0 +1,56 @@ + + +// ---------------------------------------------------------------- +// Builds libwv_odk.a, The ODK Library (libwv_odk) is used by +// the CDM and by oemcrypto implementations. +cc_library_static { + name: "libwv_odk", + include_dirs: [ + "vendor/widevine/libwvdrmengine/oemcrypto/include", + "vendor/widevine/libwvdrmengine/oemcrypto/odk/include", + ], + + srcs: [ + "src/odk.c", + "src/odk_overflow.c", + "src/odk_serialize.c", + "src/odk_timer.c", + "src/serialization_base.c", + ], + proprietary: true, + + owner: "widevine", +} + +// ---------------------------------------------------------------- +// Builds odk_test executable, which tests the ODK library. +cc_test { + name: "odk_test", + include_dirs: [ + "vendor/widevine/libwvdrmengine/oemcrypto/include", + "vendor/widevine/libwvdrmengine/oemcrypto/odk/include", + "vendor/widevine/libwvdrmengine/oemcrypto/odk/kdo/include", + ], + + // WARNING: Module tags are not supported in Soong. + // For native test binaries, use the "cc_test" module type. Some differences: + // - If you don't use gtest, set "gtest: false" + // - Binaries will be installed into /data/nativetest[64]// + // - Both 32 & 64 bit versions will be built (as appropriate) + + owner: "widevine", + proprietary: true, + + static_libs: [ + "libcdm_protos", + "libcdm", + "libwv_odk", + ], + + srcs: [ + "kdo/src/oec_util.cpp", + "test/odk_test.cpp", + "test/odk_timer_test.cpp", + ], + +} diff --git a/libwvdrmengine/oemcrypto/odk/include/OEMCryptoCENCCommon.h b/libwvdrmengine/oemcrypto/odk/include/OEMCryptoCENCCommon.h new file mode 100644 index 00000000..aa12a00c --- /dev/null +++ b/libwvdrmengine/oemcrypto/odk/include/OEMCryptoCENCCommon.h @@ -0,0 +1,153 @@ +// Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +/********************************************************************* + * OEMCryptoCENCCommon.h + * + * Common structures and error codes between WV servers and OEMCrypto. + * + *********************************************************************/ + +#ifndef OEMCRYPTO_CENC_COMMON_H_ +#define OEMCRYPTO_CENC_COMMON_H_ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// clang-format off +typedef enum OEMCryptoResult { + OEMCrypto_SUCCESS = 0, + OEMCrypto_ERROR_INIT_FAILED = 1, + OEMCrypto_ERROR_TERMINATE_FAILED = 2, + OEMCrypto_ERROR_OPEN_FAILURE = 3, + OEMCrypto_ERROR_CLOSE_FAILURE = 4, + OEMCrypto_ERROR_ENTER_SECURE_PLAYBACK_FAILED = 5, // deprecated + OEMCrypto_ERROR_EXIT_SECURE_PLAYBACK_FAILED = 6, // deprecated + OEMCrypto_ERROR_SHORT_BUFFER = 7, + OEMCrypto_ERROR_NO_DEVICE_KEY = 8, // no keybox device key. + OEMCrypto_ERROR_NO_ASSET_KEY = 9, + OEMCrypto_ERROR_KEYBOX_INVALID = 10, + OEMCrypto_ERROR_NO_KEYDATA = 11, + OEMCrypto_ERROR_NO_CW = 12, + OEMCrypto_ERROR_DECRYPT_FAILED = 13, + OEMCrypto_ERROR_WRITE_KEYBOX = 14, + OEMCrypto_ERROR_WRAP_KEYBOX = 15, + OEMCrypto_ERROR_BAD_MAGIC = 16, + OEMCrypto_ERROR_BAD_CRC = 17, + OEMCrypto_ERROR_NO_DEVICEID = 18, + OEMCrypto_ERROR_RNG_FAILED = 19, + OEMCrypto_ERROR_RNG_NOT_SUPPORTED = 20, + OEMCrypto_ERROR_SETUP = 21, + OEMCrypto_ERROR_OPEN_SESSION_FAILED = 22, + OEMCrypto_ERROR_CLOSE_SESSION_FAILED = 23, + OEMCrypto_ERROR_INVALID_SESSION = 24, + OEMCrypto_ERROR_NOT_IMPLEMENTED = 25, + OEMCrypto_ERROR_NO_CONTENT_KEY = 26, + OEMCrypto_ERROR_CONTROL_INVALID = 27, + OEMCrypto_ERROR_UNKNOWN_FAILURE = 28, + OEMCrypto_ERROR_INVALID_CONTEXT = 29, + OEMCrypto_ERROR_SIGNATURE_FAILURE = 30, + OEMCrypto_ERROR_TOO_MANY_SESSIONS = 31, + OEMCrypto_ERROR_INVALID_NONCE = 32, + OEMCrypto_ERROR_TOO_MANY_KEYS = 33, + OEMCrypto_ERROR_DEVICE_NOT_RSA_PROVISIONED = 34, + OEMCrypto_ERROR_INVALID_RSA_KEY = 35, + OEMCrypto_ERROR_KEY_EXPIRED = 36, + OEMCrypto_ERROR_INSUFFICIENT_RESOURCES = 37, + OEMCrypto_ERROR_INSUFFICIENT_HDCP = 38, + OEMCrypto_ERROR_BUFFER_TOO_LARGE = 39, + OEMCrypto_WARNING_GENERATION_SKEW = 40, // Warning, not an error. + OEMCrypto_ERROR_GENERATION_SKEW = 41, + OEMCrypto_LOCAL_DISPLAY_ONLY = 42, // Info, not an error. + OEMCrypto_ERROR_ANALOG_OUTPUT = 43, + OEMCrypto_ERROR_WRONG_PST = 44, + OEMCrypto_ERROR_WRONG_KEYS = 45, + OEMCrypto_ERROR_MISSING_MASTER = 46, + OEMCrypto_ERROR_LICENSE_INACTIVE = 47, + OEMCrypto_ERROR_ENTRY_NEEDS_UPDATE = 48, + OEMCrypto_ERROR_ENTRY_IN_USE = 49, + OEMCrypto_ERROR_USAGE_TABLE_UNRECOVERABLE = 50, // Reserved. Do not use. + OEMCrypto_KEY_NOT_LOADED = 51, // obsolete. use error 26. + OEMCrypto_KEY_NOT_ENTITLED = 52, + OEMCrypto_ERROR_BAD_HASH = 53, + OEMCrypto_ERROR_OUTPUT_TOO_LARGE = 54, + OEMCrypto_ERROR_SESSION_LOST_STATE = 55, + OEMCrypto_ERROR_SYSTEM_INVALIDATED = 56, + OEMCrypto_ERROR_LICENSE_RELOAD = 57, + OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES = 58, + OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION = 59, + /* ODK return values */ + ODK_ERROR_BASE = 1000, + ODK_ERROR_CORE_MESSAGE = ODK_ERROR_BASE, + ODK_SET_TIMER = ODK_ERROR_BASE + 1, + ODK_DISABLE_TIMER = ODK_ERROR_BASE + 2, + ODK_TIMER_EXPIRED = ODK_ERROR_BASE + 3, + ODK_UNSUPPORTED_API = ODK_ERROR_BASE + 4, +} OEMCryptoResult; +// clang-format on + +/* + * OEMCrypto_Usage_Entry_Status. + * Valid values for status in the usage table. + */ +typedef enum OEMCrypto_Usage_Entry_Status { + kUnused = 0, + kActive = 1, + kInactive = 2, // Deprecated. Used kInactiveUsed or kInactiveUnused. + kInactiveUsed = 3, + kInactiveUnused = 4, +} OEMCrypto_Usage_Entry_Status; + +/* + * OEMCrypto_Substring + * + * Used to indicate a substring of a signed message in OEMCrypto_LoadKeys and + * other functions which must verify that a parameter is contained within a + * signed message. + */ +typedef struct { + size_t offset; + size_t length; +} OEMCrypto_Substring; + +/* + * OEMCrypto_KeyObject + * Points to the relevant fields for a content key. The fields are extracted + * from the License Response message offered to OEMCrypto_LoadKeys(). Each + * field points to one of the components of the key. Key data, key control, + * and both IV fields are 128 bits (16 bytes): + * key_id - the unique id of this key. + * key_id_length - the size of key_id. OEMCrypto may assume this is at + * most 16. However, OEMCrypto shall correctly handle key id lengths + * from 1 to 16 bytes. + * key_data_iv - the IV for performing AES-128-CBC decryption of the + * key_data field. + * key_data - the key data. It is encrypted (AES-128-CBC) with the + * session's derived encrypt key and the key_data_iv. + * key_control_iv - the IV for performing AES-128-CBC decryption of the + * key_control field. + * key_control - the key control block. It is encrypted (AES-128-CBC) with + * the content key from the key_data field. + * + * The memory for the OEMCrypto_KeyObject fields is allocated and freed + * by the caller of OEMCrypto_LoadKeys(). + */ +typedef struct { + OEMCrypto_Substring key_id; + OEMCrypto_Substring key_data_iv; + OEMCrypto_Substring key_data; + OEMCrypto_Substring key_control_iv; + OEMCrypto_Substring key_control; +} OEMCrypto_KeyObject; + +#ifdef __cplusplus +} +#endif + +#endif // OEMCRYPTO_CENC_COMMON_H_ diff --git a/libwvdrmengine/oemcrypto/odk/include/odk.h b/libwvdrmengine/oemcrypto/odk/include/odk.h new file mode 100644 index 00000000..20193aaf --- /dev/null +++ b/libwvdrmengine/oemcrypto/odk/include/odk.h @@ -0,0 +1,583 @@ +/* + * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary + * source code may only be used and distributed under the Widevine Master + * License Agreement. + */ + +/********************************************************************* + * odk.h + * + * OEMCrypto v16 Core Message Serialization library + * + * For Widevine Modular DRM, there are six message types between a server and + * a client device: license request and response, provisioning request and + * response, and renewal request and response. + * + * In OEMCrypto v15 and earlier, messages from the server were parsed by the + * CDM layer above OEMCrypto; the CDM in turn gave OEMCrypto a collection of + * pointers to protected data within the message. However, the pointers + * themselves were not signed by the server. + * + * Starting from OEMCrypto v16, all fields used by OEMCrypto in each of these + * messages have been identified in the document "Widevine Core Message + * Serialization". These fields are called the core of the message. Core + * message fields are (de)serialized using the ODK, a C library provided by + * Widevine. OEMCrypto will parse and verify the core of the message with + * help from the ODK. + * + * The ODK functions that parse code will fill out structs that have similar + * formats to the function parameters of the OEMCrypto v15 functions being + * replaced. The ODK will be provided in source code and it is Widevine's + * intention that partners can build and link ODK with their implementation + * of OEMCrypto with no or few code changes. + * + * OEMCrypto implementers shall build the ODK library as part of the Trusted + * Application (TA) running in the TEE. All memory and buffers used by the + * ODK library shall be sanitized by the OEMCrypto implementer to prevent + * modification by any process running the REE. + * + * See the documents "Widevine Core Message Serialization" and "License + * Duration and Renewal" for a detailed description of the ODK API. You can + * find these documents in the widevine repository as + * docs/Widevine_Core_Message_Serialization.pdf and + * docs/License_Duration_and_Renewal.pdf + * + *********************************************************************/ + +#ifndef ODK_H_ +#define ODK_H_ + +#include +#include "OEMCryptoCENCCommon.h" +#include "odk_structs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * ODK_InitializeSessionValues + * + * Description: + * This function initializes the session's data structures. It shall be + * called from OEMCrypto_OpenSession. + * + * Parameters: + * [out] timer_limits: the session's timer limits. + * [out] clock_values: the session's clock values. + * [out] nonce_values: the session's ODK nonce values. + * [in] api_version: the API version of OEMCrypto. + * [in] session_id: the session id of the newly created session. + * + * Returns: + * OEMCrypto_SUCCESS + * OEMCrypto_ERROR_INVALID_CONTEXT + * + * Version: + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_InitializeSessionValues(ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + ODK_NonceValues* nonce_values, + uint32_t api_version, + uint32_t session_id); + +/* + * ODK_SetNonceValues + * + * Description: + * This function sets the nonce value in the session's nonce structure. It + * shall be called from OEMCrypto_GenerateNonce. + * + * Parameters: + * [in/out] nonce_values: the session's nonce data. + * [in] nonce: the new nonce that was just generated. + * + * Returns: + * true on success + * + * Version: + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_SetNonceValues(ODK_NonceValues* nonce_values, + uint32_t nonce); + +/* + * ODK_InitializeClockValues + * + * Description: + * This function initializes the clock values in the session clock_values + * structure. It shall be called from OEMCrypto_PrepAndSignLicenseRequest. + * + * Parameters: + * [in/out] clock_values: the session's clock data. + * [in] system_time_seconds: the current time on OEMCrypto's monotonic clock. + * + * Returns: + * OEMCrypto_SUCCESS + * OEMCrypto_ERROR_INVALID_CONTEXT + * + * Version: + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_InitializeClockValues(ODK_ClockValues* clock_values, + uint64_t system_time_seconds); + +/* + * ODK_ReloadClockValues + * + * Description: + * This function sets the values in the clock_values structure. It shall be + * called from OEMCrypto_LoadUsageEntry. + * + * Parameters: + * [in/out] clock_values: the session's clock data. + * [in] time_of_license_signed: the value time_license_received from the + * loaded usage entry. + * [in] time_of_first_decrypt: the value time_of_first_decrypt from the + * loaded usage entry. + * [in] time_of_last_decrypt: the value time_of_last_decrypt from the loaded + * usage entry. + * [in] status: the value status from the loaded usage entry. + * [in] system_time_seconds: the current time on OEMCrypto's monotonic clock. + * + * Returns: + * OEMCrypto_SUCCESS + * OEMCrypto_ERROR_INVALID_CONTEXT + * + * Version: + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_ReloadClockValues(ODK_ClockValues* clock_values, + uint64_t time_of_license_signed, + uint64_t time_of_first_decrypt, + uint64_t time_of_last_decrypt, + enum OEMCrypto_Usage_Entry_Status status, + uint64_t system_time_seconds); + +/* + * ODK_AttemptFirstPlayback + * + * Description: + * This updates the clock values, and determines if playback may start based + * on the given system time. It uses the values in clock_values to determine + * if this is the first playback for the license or the first playback for + * just this session. + * + * This shall be called from the first call in a session to any of + * OEMCrypto_DecryptCENC or any of the OEMCrypto_Generic* functions. + * + * If OEMCrypto uses a hardware timer, and this function returns + * ODK_SET_TIMER, then the timer should be set to the value pointed to by + * timer_value. + * + * Parameters: + * [in] system_time_seconds: the current time on OEMCrypto's monotonic clock, + * in seconds. + * [in] timer_limits: timer limits specified in the license. + * [in/out] clock_values: the sessions clock values. + * [out] timer_value: set to the new timer value. Only used if the return + * value is ODK_SET_TIMER. This must be non-null if OEMCrypto uses a + * hardware timer. + * + * Returns: + * ODK_SET_TIMER: Success. The timer should be reset to the specified value + * and playback is allowed. + * ODK_DISABLE_TIMER: Success, but disable timer. Unlimited playback is + * allowed. + * ODK_TIMER_EXPIRED: Set timer as disabled. Playback is not allowed. + * + * Version: + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_AttemptFirstPlayback(uint64_t system_time_seconds, + const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + uint64_t* timer_value); + +/* + * ODK_UpdateLastPlaybackTime + * + * Description: + * Vendors that do not implement their own timer should call + * ODK_UpdateLastPlaybackTime regularly during playback. This updates the + * clock values, and determines if playback may continue based on the given + * system time. This shall be called from any of OEMCrypto_DecryptCENC or any + * of the OEMCrypto_Generic* functions. + * + * All Vendors (i.e. those that do or do not implement their own timer) shall + * call ODK_UpdateLastPlaybackTime from the function + * OEMCrypto_UpdateUsageEntry before updating the usage entry so that the + * clock values are accurate. + * + * Parameters: + * [in] system_time_seconds: the current time on OEMCrypto's monotonic clock, + * in seconds. + * [in] timer_limits: timer limits specified in the license. + * [in/out] clock_values: the sessions clock values. + * + * Returns: + * OEMCrypto_SUCCESS: Success. Playback is allowed. + * ODK_TIMER_EXPIRED: Set timer as disabled. Playback is not allowed. + * + * Version: + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_UpdateLastPlaybackTime(uint64_t system_time_seconds, + const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values); + +/* + * ODK_DeactivateUsageEntry + * + * Description: + * This function modifies the session's clock values to indicate that the + * license has been deactiviated. It shall be called from + * OEMCrypto_DeactivateUsageEntry + * + * Parameters: + * [in/out] clock_values: the sessions clock values. + * + * Returns: + * OEMCrypto_SUCCESS + * OEMCrypto_ERROR_INVALID_CONTEXT + * + * Version: + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_DeactivateUsageEntry(ODK_ClockValues* clock_values); + +/* + * ODK_PrepareCoreLicenseRequest + * + * Description: + * Modifies the message to include a core license request at the beginning of + * the message buffer. The values in nonce_values are used to populate the + * message. + * + * This shall be called by OEMCrypto from OEMCrypto_PrepAndSignLicenseRequest. + * + * NOTE: if message pointer is null and/or input core_message_size is zero, + * this function returns OEMCrypto_ERROR_SHORT_BUFFER and sets output + * core_message_size to the size needed. + * + * Parameters: + * [in/out] message: Pointer to memory for the entire message. Modified by + * the ODK library. + * [in] message_length: length of the entire message buffer. + * [in/out] core_message_size: length of the core message at the beginning of + * the message. (in) size of buffer reserved for the core message, in + * bytes. (out) actual length of the core message, in bytes. + * [in] nonce_values: pointer to the session's nonce data. + * + * Returns: + * OEMCrypto_SUCCESS + * OEMCrypto_ERROR_SHORT_BUFFER if core_message_size is too small + * OEMCrypto_ERROR_INVALID_CONTEXT + * + * Version: + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_PrepareCoreLicenseRequest( + uint8_t* message, size_t message_length, size_t* core_message_size, + const ODK_NonceValues* nonce_values); + +/* + * ODK_PrepareCoreRenewalRequest + * + * Description: + * Modifies the message to include a core renewal request at the beginning of + * the message buffer. The values in nonce_values, clock_values and + * system_time_seconds are used to populate the message. The nonce_values + * should match those from the license. + * + * This shall be called by OEMCrypto from OEMCrypto_PrepAndSignRenewalRequest. + * + * NOTE: if message pointer is null and/or input core_message_size is zero, + * this function returns OEMCrypto_ERROR_SHORT_BUFFER and sets output + * core_message_size to the size needed. + * + * Parameters: + * [in/out] message: Pointer to memory for the entire message. Modified by + * the ODK library. + * [in] message_length: length of the entire message buffer. + * [in/out] core_message_size: length of the core message at the beginning of + * the message. (in) size of buffer reserved for the core message, in + * bytes. (out) actual length of the core message, in bytes. + * [in] nonce_values: pointer to the session's nonce data. + * [in] clock_values: the session's clock values. + * [in] system_time_seconds: the current time on OEMCrypto's clock, in + * seconds. + * + * Returns: + * OEMCrypto_SUCCESS + * OEMCrypto_ERROR_SHORT_BUFFER if core_message_size is too small + * OEMCrypto_ERROR_INVALID_CONTEXT + * + * Version: + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_PrepareCoreRenewalRequest( + uint8_t* message, size_t message_length, size_t* core_message_size, + const ODK_NonceValues* nonce_values, const ODK_ClockValues* clock_values, + uint64_t system_time_seconds); + +/* + * ODK_PrepareCoreProvisioningRequest + * + * Description: + * Modifies the message to include a core provisioning request at the + * beginning of the message buffer. The values in nonce_values are used to + * populate the message. + * + * This shall be called by OEMCrypto from + * OEMCrypto_PrepAndSignProvisioningRequest. + * + * The buffer device_id shall be the same string returned by + * OEMCrypto_GetDeviceID. The device ID shall be unique to the device, and + * stable across reboots and factory resets for an L1 device. + * + * NOTE: if message pointer is null and/or input core_message_size is zero, + * this function returns OEMCrypto_ERROR_SHORT_BUFFER and sets output + * core_message_size to the size needed. + * + * Parameters: + * [in/out] message: Pointer to memory for the entire message. Modified by + * the ODK library. + * [in] message_length: length of the entire message buffer. + * [in/out] core_message_size: length of the core message at the beginning of + * the message. (in) size of buffer reserved for the core message, in + * bytes. (out) actual length of the core message, in bytes. + * [in] nonce_values: pointer to the session's nonce data. + * [in] device_id: For devices with a keybox, this is the device ID from the + * keybox. For devices with an OEM Certificate, this is a device unique + * id string. + * [in] device_id_length: length of device_id. The device ID can be at most + * 64 bytes. + * + * Returns: + * OEMCrypto_SUCCESS + * OEMCrypto_ERROR_SHORT_BUFFER if core_message_size is too small + * OEMCrypto_ERROR_INVALID_CONTEXT + * + * Version: + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_PrepareCoreProvisioningRequest( + uint8_t* message, size_t message_length, size_t* core_message_size, + const ODK_NonceValues* nonce_values, const uint8_t* device_id, + size_t device_id_length); + +/* + * ODK_InitializeV15Values + * + * Description: + * This function sets all limits in the timer_limits struct to the + * key_duration and initializes the other values. The field + * nonce_values.api_level will be set to 15. It shall be called from + * OEMCrypto_LoadKeys when loading a legacy license. + * + * Parameters: + * [out] timer_limits: The session's timer limits. + * [in/out] clock_values: The session's clock values. + * [in/out] nonce_values: The session's ODK nonce values. + * [in] key_duration: The duration from the first key's key control block. In + * practice, the key duration is the same for all keys and is the same + * as the license duration. + * [in] system_time_seconds: The current time on the system clock, as + * described in the document "License Duration and Renewal". + * + * Returns: + * OEMCrypto_SUCCESS + * OEMCrypto_ERROR_INVALID_CONTEXT + * + * Version: + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_InitializeV15Values(ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + ODK_NonceValues* nonce_values, + uint32_t key_duration, + uint64_t system_time_seconds); + +/* + * ODK_ParseLicense + * + * Description: + * The function ODK_ParseLicense will parse the message and verify fields in + * the message. + * + * If the message does not parse correctly, ODK_VerifyAndParseLicense will + * return ODK_ERROR_CORE_MESSAGE that OEMCrypto should return to the CDM + * layer above. + * + * If the api in the message is not 16, then ODK_UNSUPPORTED_API is returned. + * + * If initial_license_load is true, and nonce_required in the license is + * true, then the ODK library shall verify that nonce_values->nonce and + * nonce_values->session_id are the same as those in the message. If + * verification fails, then it shall return OEMCrypto_ERROR_INVALID_NONCE. + * + * If initial_license_load is false, and nonce_required is true, then + * ODK_ParseLicense will set the values in nonce_values from those in the + * message. + * + * The function ODK_ParseLicense will verify each substring points to a + * location in the message body. The message body is the buffer starting at + * message + core_message_length with size message_length - + * core_message_length. + * + * If initial_license_load is true, then ODK_ParseLicense shall verify that + * hash matches request_hash in the parsed license. If verification fails, + * then it shall return ODK_ERROR_CORE_MESSAGE. + * + * If usage_entry_present is true, then ODK_ParseLicense shall verify that + * the pst in the license has a nonzero length. + * + * Parameters: + * [in] message: pointer to the message buffer. + * [in] message_length: length of the entire message buffer. + * [in] core_message_size: length of the core message, at the beginning of + * the message buffer. + * [in] initial_license_load: true when called for OEMCrypto_LoadLicense and + * false when called for OEMCrypto_ReloadLicense. + * [in] usage_entry_present: true if the session has a new usage entry + * associated with it created via OEMCrypto_CreateNewUsageEntry. + * [in/out] timer_limits: The session's timer limits. These will be updated. + * [in/out] clock_values: The session's clock values. These will be updated. + * [in/out] nonce_values: The session's nonce values. These will be updated. + * [out] parsed_license: the destination for the data. + * + * Returns: + * OEMCrypto_SUCCESS + * ODK_ERROR_CORE_MESSAGE if the message did not parse correctly, or there + * were other incorrect values. An error should be returned to the CDM + * layer. + * ODK_UNSUPPORTED_API + * OEMCrypto_ERROR_INVALID_NONCE + * + * Version: + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_ParseLicense( + const uint8_t* message, size_t message_length, size_t core_message_length, + bool initial_license_load, bool usage_entry_present, + const uint8_t request_hash[ODK_SHA256_HASH_SIZE], + ODK_TimerLimits* timer_limits, ODK_ClockValues* clock_values, + ODK_NonceValues* nonce_values, ODK_ParsedLicense* parsed_license); + +/* + * ODK_ParseRenewal + * + * Description: + * The function ODK_ParseRenewal will parse the message and verify. If the + * message does not parse correctly, an error of ODK_ERROR_CORE_MESSAGE is + * returned. + * + * ODK_ParseRenewal shall verify that all fields in nonce_values match those + * in the license. Otherwise it shall return OEMCrypto_ERROR_INVALID_NONCE. + * + * After parsing the message, this function updates the clock_values based on + * the timer_limits and the current system time. If playback may not + * continue, then ODK_TIMER_EXPIRED is returned. + * + * If playback may continue, a return value of ODK_SET_TIMER or + * ODK_TIMER_EXPIRED is returned. If the return value is ODK_SET_TIMER, then + * playback may continue until the timer expires. If the return value is + * ODK_DISABLE_TIMER, then playback time is not limited. + * + * If OEMCrypto uses a hardware timer, and this function returns + * ODK_SET_TIMER, then the timer should be set to the value pointed to by + * timer_value. + * + * Parameters: + * [in] message: pointer to the message buffer. + * [in] message_length: length of the entire message buffer. + * [in] core_message_size: length of the core message, at the beginning of + * the message buffer. + * [in] nonce_values: pointer to the session's nonce data. + * [in] system_time_seconds: the current time on OEMCrypto's clock, in + * seconds. + * [in] timer_limits: timer limits specified in the license. + * [in/out] clock_values: the sessions clock values. + * [out] timer_value: set to the new timer value. Only used if the return + * value is ODK_SET_TIMER. This must be non-null if OEMCrypto uses a + * hardware timer. + * + * Returns: + * ODK_ERROR_CORE_MESSAGE if the message did not parse correctly, or there + * were other incorrect values. An error should be returned to the CDM + * layer. + * ODK_SET_TIMER: Success. The timer should be reset to the specified timer + * value. + * ODK_DISABLE_TIMER: Success, but disable timer. Unlimited playback is + * allowed. + * ODK_TIMER_EXPIRED: Set timer as disabled. Playback is not allowed. + * ODK_UNSUPPORTED_API + * OEMCrypto_ERROR_INVALID_NONCE + * + * Version: + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_ParseRenewal(const uint8_t* message, size_t message_length, + size_t core_message_length, + const ODK_NonceValues* nonce_values, + uint64_t system_time_seconds, + const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + uint64_t* timer_value); + +/* + * ODK_ParseProvisioning + * + * Description: + * The function ODK_ParseProvisioning will parse the message and verify the + * nonce values match those in the license. + * + * If the message does not parse correctly, ODK_ParseProvisioning will return + * an error that OEMCrypto should return to the CDM layer above. + * + * If the api in the message is larger than 16, then ODK_UNSUPPORTED_API is + * returned. + * + * ODK_ParseProvisioning shall verify that nonce_values->nonce and + * nonce_values->session_id are the same as those in the message. Otherwise + * it shall return OEMCrypto_ERROR_INVALID_NONCE. + * + * The function ODK_ParseProvisioning will verify each substring points to a + * location in the message body. The message body is the buffer starting at + * message + core_message_length with size message_length - + * core_message_length. + * + * Parameters: + * [in] message: pointer to the message buffer. + * [in] message_length: length of the entire message buffer. + * [in] core_message_size: length of the core message, at the beginning of + * the message buffer. + * [in] nonce_values: pointer to the session's nonce data. + * [in] device_id: a pointer to a buffer containing the device ID of the + * device. The ODK function will verify it matches that in the message. + * [in] device_id_length: the length of the device ID. + * [out] parsed_response: destination for the parse data. + * + * Returns: + * OEMCrypto_SUCCESS + * ODK_ERROR_CORE_MESSAGE if the message did not parse correctly, or there + * were other incorrect values. An error should be returned to the CDM + * layer. + * ODK_UNSUPPORTED_API + * OEMCrypto_ERROR_INVALID_NONCE + * + * Version: + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_ParseProvisioning( + const uint8_t* message, size_t message_length, size_t core_message_length, + const ODK_NonceValues* nonce_values, const uint8_t* device_id, + size_t device_id_length, ODK_ParsedProvisioning* parsed_response); + +#ifdef __cplusplus +} +#endif + +#endif // ODK_H_ diff --git a/libwvdrmengine/oemcrypto/odk/include/odk_assert.h b/libwvdrmengine/oemcrypto/odk/include/odk_assert.h new file mode 100644 index 00000000..e1a21fd0 --- /dev/null +++ b/libwvdrmengine/oemcrypto/odk/include/odk_assert.h @@ -0,0 +1,27 @@ + +/* + * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary + * source code may only be used and distributed under the Widevine Master + * License Agreement. + */ + +#ifndef ODK_ASSERT_H_ +#define ODK_ASSERT_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#if (__STDC_VERSION__ >= 201112L) +# include +# define odk_static_assert static_assert +#else +# define odk_static_assert(msg, e) \ + enum { odk_static_assert = 1 / (!!((msg) && (e))) }; +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* ODK_ASSERT_H_ */ diff --git a/libwvdrmengine/oemcrypto/odk/include/odk_overflow.h b/libwvdrmengine/oemcrypto/odk/include/odk_overflow.h new file mode 100644 index 00000000..32aebe76 --- /dev/null +++ b/libwvdrmengine/oemcrypto/odk/include/odk_overflow.h @@ -0,0 +1,33 @@ +/* + * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary + * source code may only be used and distributed under the Widevine Master + * License Agreement. + */ + +#ifndef ODK_OVERFLOW_H_ +#define ODK_OVERFLOW_H_ + +#ifdef __cplusplus +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 +} +#endif + +#endif /* ODK_OVERFLOW_H_ */ diff --git a/libwvdrmengine/oemcrypto/odk/include/odk_serialize.h b/libwvdrmengine/oemcrypto/odk/include/odk_serialize.h new file mode 100644 index 00000000..c9488d71 --- /dev/null +++ b/libwvdrmengine/oemcrypto/odk/include/odk_serialize.h @@ -0,0 +1,44 @@ +/* + * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary + * source code may only be used and distributed under the Widevine Master + * License Agreement. + */ + +/* + * This code is auto-generated, do not edit + */ +#ifndef ODKITEE_SERIALIZER_H_ +#define ODKITEE_SERIALIZER_H_ + +#include "odk_structs_priv.h" +#include "serialization_base.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* odk pack */ +void Pack_ODK_PreparedLicense(Message* msg, ODK_PreparedLicense const* obj); +void Pack_ODK_RenewalMessage(Message* msg, ODK_RenewalMessage const* obj); +void Pack_ODK_ProvisioningMessage(Message* msg, + ODK_ProvisioningMessage const* obj); + +/* odk unpack */ +void Unpack_ODK_LicenseResponse(Message* msg, ODK_LicenseResponse* obj); +void Unpack_ODK_RenewalMessage(Message* msg, ODK_RenewalMessage* obj); +void Unpack_ODK_ProvisioningResponse(Message* msg, + ODK_ProvisioningResponse* obj); + +/* kdo pack */ +void Pack_ODK_LicenseResponse(Message* msg, ODK_LicenseResponse const* obj); +void Pack_ODK_ProvisioningResponse(Message* msg, + ODK_ProvisioningResponse const* obj); + +/* kdo unpack */ +void Unpack_ODK_PreparedLicense(Message* msg, ODK_PreparedLicense* obj); +void Unpack_ODK_ProvisioningMessage(Message* msg, ODK_ProvisioningMessage* obj); + +#ifdef __cplusplus +} // extern "C" +#endif +#endif /* ODKITEE_SERIALIZER_H_ */ diff --git a/libwvdrmengine/oemcrypto/odk/include/odk_structs.h b/libwvdrmengine/oemcrypto/odk/include/odk_structs.h new file mode 100644 index 00000000..52b01bd6 --- /dev/null +++ b/libwvdrmengine/oemcrypto/odk/include/odk_structs.h @@ -0,0 +1,96 @@ +/* + * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary + * source code may only be used and distributed under the Widevine Master + * License Agreement. + */ + +#ifndef ODK_STRUCTS_H_ +#define ODK_STRUCTS_H_ + +#include +#include "OEMCryptoCENCCommon.h" + +#define ODK_MAX_NUM_KEYS 32 +#define ODK_DEVICE_ID_LEN_MAX 64 +#define ODK_SHA256_HASH_SIZE 32 + +/* + * ODK_TimerLimits is filled out by the function ODK_ParseLicense. + * + * The fields in this structure are defined in the core license response + * message. This structure should be kept as part of the session and used + * when calling the ODK timer functions described in the document "License + * Duration and Renewal" distributed as part of the OEMCrypto v16 design. + */ +typedef struct { + uint32_t /*boolean*/ soft_expiry; + uint64_t earliest_playback_start_seconds; // seconds since license signed. + uint64_t latest_playback_start_seconds; // seconds since license signed. + uint64_t initial_playback_duration_seconds; // seconds since playback start. + uint64_t renewal_playback_duration_seconds; // seconds since renewal signed. + uint64_t license_duration_seconds; // seconds since license signed. +} ODK_TimerLimits; + +/* + * ODK_ParsedLicense holds fields from the core license response. + */ +typedef struct { + OEMCrypto_Substring enc_mac_keys_iv; + OEMCrypto_Substring enc_mac_keys; + OEMCrypto_Substring pst; + OEMCrypto_Substring srm_restriction_data; + uint32_t /* OEMCrypto_LicenseType */ license_type; + uint32_t nonce_required; + ODK_TimerLimits timer_limits; + uint8_t request_hash[ODK_SHA256_HASH_SIZE]; + uint32_t key_array_length; /* num_keys */ + OEMCrypto_KeyObject key_array[ODK_MAX_NUM_KEYS]; +} ODK_ParsedLicense; + +/* + * ODK_ParsedProvisioning holds fields from the core provisioning response. + */ +typedef struct { + uint32_t key_type; + OEMCrypto_Substring enc_private_key; + OEMCrypto_Substring enc_private_key_iv; + OEMCrypto_Substring encrypted_message_key; /* Used for Prov 3.0 */ +} ODK_ParsedProvisioning; + +/* + * ODK_ClockValues keeps information about a session's current clock values + * and timers. + * + * Most of the fields in this structure are saved in the usage entry for each + * session. This structure should be initialized when a usage entry is + * created or loaded, and should be used to save a usage entry. It is + * updated using ODK functions listed in the document "License Duration and + * Renewal". The time values are based on OEMCrypto’s system clock. + */ +typedef struct { + uint64_t time_of_license_signed; + uint64_t time_of_first_decrypt; + uint64_t time_of_last_decrypt; + uint64_t time_when_timer_expires; + uint32_t timer_status; + enum OEMCrypto_Usage_Entry_Status status; +} ODK_ClockValues; + +/* + * ODK_NonceValues are used to match a license or provisioning request to a + * license or provisioning response. For this reason, the api_version might be + * lower than that supported by OEMCrypto. The api_version matches the version + * of the license. Similarly the nonce and session_id match the session that + * generated the license request. For an offline license, these might not match + * the session that is loading the license. We use the nonce to prevent a + * license from being replayed. By also including a session_id in the license + * request and license response, we prevent an attack using the birthday paradox + * to generate nonce collisions on a single device. + */ +typedef struct { + uint32_t api_version; + uint32_t nonce; + uint32_t session_id; +} ODK_NonceValues; + +#endif // ODK_STRUCTS_H_ diff --git a/libwvdrmengine/oemcrypto/odk/include/odk_structs_priv.h b/libwvdrmengine/oemcrypto/odk/include/odk_structs_priv.h new file mode 100644 index 00000000..8b3ee35a --- /dev/null +++ b/libwvdrmengine/oemcrypto/odk/include/odk_structs_priv.h @@ -0,0 +1,54 @@ +/* + * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary + * source code may only be used and distributed under the Widevine Master + * License Agreement. + */ + +#ifndef ODK_STRUCTS_PRIV_H_ +#define ODK_STRUCTS_PRIV_H_ + +#include +#include "OEMCryptoCENCCommon.h" +#include "odk_structs.h" + +typedef enum { + ODK_License_Request_Type = 1, + ODK_License_Response_Type = 2, + ODK_Renewal_Request_Type = 3, + ODK_Renewal_Response_Type = 4, + ODK_Provisioning_Request_Type = 5, + ODK_Provisioning_Response_Type = 6, +} ODK_MessageType; + +typedef struct { + uint32_t message_type; + uint32_t message_length; + ODK_NonceValues nonce_values; +} ODK_CoreMessage; + +typedef struct { + ODK_CoreMessage core_message; +} ODK_PreparedLicense; + +typedef struct { + ODK_CoreMessage core_message; + uint64_t playback_time; +} ODK_RenewalMessage; + +typedef struct { + ODK_CoreMessage core_message; + uint32_t device_id_length; + uint8_t device_id[ODK_DEVICE_ID_LEN_MAX]; +} ODK_ProvisioningMessage; + +typedef struct { + ODK_CoreMessage core_message; + ODK_ParsedLicense* parsed_license; +} ODK_LicenseResponse; + +typedef struct { + ODK_ProvisioningMessage core_provisioning; + ODK_ParsedProvisioning* parsed_provisioning; +} ODK_ProvisioningResponse; + +#endif // ODK_STRUCTS_PRIV_H_ diff --git a/libwvdrmengine/oemcrypto/odk/include/serialization_base.h b/libwvdrmengine/oemcrypto/odk/include/serialization_base.h new file mode 100644 index 00000000..cc1a3d12 --- /dev/null +++ b/libwvdrmengine/oemcrypto/odk/include/serialization_base.h @@ -0,0 +1,91 @@ +/* + * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary + * source code may only be used and distributed under the Widevine Master + * License Agreement. + */ + +#ifndef ODKITEE_SERIALIZATION_BASE_H_ +#define ODKITEE_SERIALIZATION_BASE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include "OEMCryptoCENCCommon.h" + +#define SIZE_OF_MESSAGE_STRUCT 64 + +/* + * Description: + * Point |msg| to stack-array |blk|. + * |blk| is guaranteed large enough to hold a |Message| struct. + * |blk| cannot be used in the same scope as a variable name. + * |msg| points to valid memory in the same scope |AllocateMessage| is used. + * Parameters: + * msg: pointer to pointer to |Message| struct + * blk: variable name for stack-array + */ +#define AllocateMessage(msg, blk) \ + uint8_t blk[SIZE_OF_MESSAGE_STRUCT]; \ + *(msg) = (Message*)(blk); + +typedef struct _Message Message; + +bool ValidMessage(Message* message); + +void Pack_uint32_t(Message* message, const uint32_t* value); +void Pack_uint64_t(Message* message, const uint64_t* value); +void PackArray(Message* message, const uint8_t* base, size_t size); +void Pack_OEMCrypto_Substring(Message* msg, const OEMCrypto_Substring* obj); + +void Unpack_uint32_t(Message* message, uint32_t* value); +void Unpack_uint64_t(Message* message, uint64_t* value); +void UnpackArray(Message* message, uint8_t* base, size_t size); /* copy out */ +void Unpack_OEMCrypto_Substring(Message* msg, OEMCrypto_Substring* obj); + +typedef enum { + MESSAGE_STATUS_OK, + MESSAGE_STATUS_UNKNOWN_ERROR, + MESSAGE_STATUS_OVERFLOW_ERROR, + MESSAGE_STATUS_UNDERFLOW_ERROR, + MESSAGE_STATUS_PARSE_ERROR, + MESSAGE_STATUS_NULL_POINTER_ERROR, + MESSAGE_STATUS_API_VALUE_ERROR +} MessageStatus; + +/* + * Create a message from a buffer. The message structure consumes the first + * sizeof(Message) bytes of the buffer. The caller is responsible for ensuring + * that the buffer remains allocated for the lifetime of the message. + */ +Message* CreateMessage(uint8_t* buffer, size_t buffer_size); + +/* + * Initialize a message structure to reference a separate buffer. The caller + * is responsible for ensuring that the buffer remains allocated for the + * lifetime of the message. + */ +void InitMessage(Message* message, uint8_t* buffer, size_t capacity); + +/* + * Reset an existing the message to an empty state + */ +void ResetMessage(Message* message); +uint8_t* GetBase(Message* message); +size_t GetCapacity(Message* message); +size_t GetSize(Message* message); +void SetSize(Message* message, size_t size); +MessageStatus GetStatus(Message* message); +void SetStatus(Message* message, MessageStatus status); +size_t GetOffset(Message* message); + +size_t SizeOfMessageStruct(); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // ODKITEE_SERIALIZATION_BASE_H_ diff --git a/libwvdrmengine/oemcrypto/odk/kdo/include/oec_util.h b/libwvdrmengine/oemcrypto/odk/kdo/include/oec_util.h new file mode 100644 index 00000000..20bee97b --- /dev/null +++ b/libwvdrmengine/oemcrypto/odk/kdo/include/oec_util.h @@ -0,0 +1,172 @@ +/* + * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary + * source code may only be used and distributed under the Widevine Master + * License Agreement. + */ + +// clang-format off +/********************************************************************* + * oec_util.h + * + * OEMCrypto v16 Core Message Serialization library counterpart (a.k.a. KDO) + * + * For Widevine Modular DRM, there are six message types between a server and + * a client device: license request and response, provisioning request and + * response, and renewal request and response. + * + * In OEMCrypto v15 and earlier, messages from the server were parsed by the + * CDM layer above OEMCrypto; the CDM in turn gave OEMCrypto a collection of + * pointers to protected data within the message. However, the pointers + * themselves were not signed by the server. + * + * Starting from OEMCrypto v16, all fields used by OEMCrypto in each of these + * messages have been identified in the document "Widevine Core Message + * Serialization". These fields are called the core of the message. Core + * message fields are (de)serialized using the ODK, a C library provided by + * Widevine. OEMCrypto will parse and verify the core of the message with + * help from the ODK. + * + * The KDO library is the counterpart of ODK used in the CDM & Widevine + * servers. For each message type generated by the ODK, KDO provides a + * corresponding parser. For each message type to be parsed by the ODK, + * KDO provides a corresponding writer. + * + * Table: ODK vs KDO (s: serialize; d: deserialize) + * +----------------------------------------+------------------------------------+ + * | ODK | KDO | + * +---+------------------------------------+---+--------------------------------+ + * | s | ODK_PrepareCoreLicenseRequest | d | ParseLicenseRequest | + * | +------------------------------------+ +--------------------------------+ + * | | ODK_PrepareCoreRenewalRequest | | ParseRenewalRequest | + * | +------------------------------------+ +--------------------------------+ + * | | ODK_PrepareCoreProvisioningRequest | | ParseProvisioningRequest | + * +---+------------------------------------+---+--------------------------------+ + * | d | ODK_ParseLicense | s | CreateCoreLicenseResponse | + * | +------------------------------------+ +--------------------------------+ + * | | ODK_ParseRenewal | | CreateCoreRenewalResponse | + * | +------------------------------------+ +--------------------------------+ + * | | ODK_ParseProvisioning | | CreateCoreProvisioningResponse | + * +---+------------------------------------+---+--------------------------------+ + * + *********************************************************************/ +// clang-format on + +#ifndef OEC_UTIL_H_ +#define OEC_UTIL_H_ + +#include +#include + +#include "odk_structs.h" + +using namespace std; + +namespace oec_util { + +// @ input/output structs + +/** + * Output structure for ParseLicenseRequest + * Input structure for CreateCoreLicenseResponse + */ +struct ODK_LicenseRequest { + uint32_t api_version; + uint32_t nonce; + uint32_t session_id; +}; + +/** + * Output structure for ParseRenewalRequest + * Input structure for CreateCoreRenewalResponse + */ +struct ODK_RenewalRequest { + uint32_t api_version; + uint32_t nonce; + uint32_t session_id; + uint64_t playback_time; +}; + +/** + * Output structure for ParseProvisioningRequest + * Input structure for CreateCoreProvisioningResponse + */ +struct ODK_ProvisioningRequest { + uint32_t api_version; + uint32_t nonce; + uint32_t session_id; + string device_id; +}; + +// @ public parse request (deserializer) functions + +/** + * Counterpart (deserializer) of ODK_PrepareCoreLicenseRequest (serializer) + * + * Parameters: + * [in] oemcrypto_core_message + * [out] core_license_request + */ +bool ParseLicenseRequest(const string& oemcrypto_core_message, + ODK_LicenseRequest* core_license_request); + +/** + * Counterpart (deserializer) of ODK_PrepareCoreRenewalRequest (serializer) + * + * Parameters: + * [in] oemcrypto_core_message + * [out] core_renewal_request + */ +bool ParseRenewalRequest(const string& oemcrypto_core_message, + ODK_RenewalRequest* core_renewal_request); + +/** + * Counterpart (deserializer) of ODK_PrepareCoreProvisioningRequest (serializer) + * + * Parameters: + * [in] oemcrypto_core_message + * [out] core_provisioning_request + */ +bool ParseProvisioningRequest( + const string& oemcrypto_core_message, + ODK_ProvisioningRequest* core_provisioning_request); + +// @ public create response (serializer) functions + +/** + * Counterpart (serializer) of ODK_ParseLicense (deserializer) + * struct-input variant + * + * Parameters: + * [in] parsed_lic + * [in] core_request + * [out] oemcrypto_core_message + */ +bool CreateCoreLicenseResponse(const ODK_ParsedLicense& parsed_lic, + const ODK_LicenseRequest& core_request, + string* oemcrypto_core_message); + +/** + * Counterpart (serializer) of ODK_ParseRenewal (deserializer) + * + * Parameters: + * [in] core_request + * [out] oemcrypto_core_message + */ +bool CreateCoreRenewalResponse(const ODK_RenewalRequest& core_request, + string* oemcrypto_core_message); + +/** + * Counterpart (serializer) of ODK_ParseProvisioning (deserializer) + * struct-input variant + * + * Parameters: + * [in] parsed_prov + * [in] core_request + * [out] oemcrypto_core_message + */ +bool CreateCoreProvisioningResponse(const ODK_ParsedProvisioning& parsed_prov, + const ODK_ProvisioningRequest& core_request, + string* oemcrypto_core_message); +} // namespace oec_util + +#endif // OEC_UTIL_H_ diff --git a/libwvdrmengine/oemcrypto/odk/kdo/include/oec_util_proto.h b/libwvdrmengine/oemcrypto/odk/kdo/include/oec_util_proto.h new file mode 100644 index 00000000..f9d8017a --- /dev/null +++ b/libwvdrmengine/oemcrypto/odk/kdo/include/oec_util_proto.h @@ -0,0 +1,59 @@ +/* + * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary + * source code may only be used and distributed under the Widevine Master + * License Agreement. + */ + +/********************************************************************* + * oec_util_proto.h + * + * These functions are an extension of those found in oec_util.h. The + * difference is that these use the license and provisioning messages + * in protobuf format to create the core message. + *********************************************************************/ + +#ifndef OEC_UTIL_PROTO_H_ +#define OEC_UTIL_PROTO_H_ + +#include +#include + +#include "license_protocol.pb.h" +#include "oec_util.h" + +using namespace std; +using video_widevine::License; +using video_widevine::License_KeyContainer; + +namespace oec_util { + +// @ public create response (serializer) functions + +/** + * Counterpart (serializer) of ODK_ParseLicense (deserializer) + * + * Parameters: + * [in] license + * [in] core_request + * [out] oemcrypto_core_message + */ +bool CreateCoreLicenseResponse(const video_widevine::License& license, + const ODK_LicenseRequest& core_request, + string* oemcrypto_core_message); + +/** + * Counterpart (serializer) of ODK_ParseProvisioning (deserializer) + * + * Parameters: + * [in] provisioning_response + * [in] core_request + * [out] oemcrypto_core_message + */ +bool CreateCoreProvisioningResponse( + const video_widevine::ProvisioningResponse& provisioning_response, + const ODK_ProvisioningRequest& core_request, + string* oemcrypto_core_message); + +} // namespace oec_util + +#endif // OEC_UTIL_PROTO_H_ diff --git a/libwvdrmengine/oemcrypto/odk/kdo/src/oec_util.cpp b/libwvdrmengine/oemcrypto/odk/kdo/src/oec_util.cpp new file mode 100644 index 00000000..affd9792 --- /dev/null +++ b/libwvdrmengine/oemcrypto/odk/kdo/src/oec_util.cpp @@ -0,0 +1,209 @@ +/* + * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary + * source code may only be used and distributed under the Widevine Master + * License Agreement. + */ + +#include "oec_util.h" + +#include +#include +#include +#include +#include +#include + +#include "odk_overflow.h" +#include "odk_serialize.h" +#include "odk_structs.h" +#include "odk_structs_priv.h" +#include "oemcrypto_types.h" +#include "serialization_base.h" + +using namespace oec_util; + +namespace oec_util { + +namespace { + +/* @ private functions */ + +const int CURRENT_OEC_VERSION = 16; + +/** + * Template for parsing requests + * + * Template arguments: + * S: kdo output struct + * T: struct serialized by odk + * U: auto-generated deserializing function for |T| + */ +template +bool ParseRequest(uint32_t message_type, const string& oemcrypto_core_message, + S* core_request, T* prepared, const U unpacker) { + if (!core_request) { + return false; + } + + const uint8_t* buf = + reinterpret_cast(oemcrypto_core_message.c_str()); + size_t buf_length = oemcrypto_core_message.size(); + + Message* msg = NULL; + AllocateMessage(&msg, message_block); + InitMessage(msg, const_cast(buf), buf_length); + SetSize(msg, buf_length); + + unpacker(msg, prepared); + if (!ValidMessage(msg)) { + return false; + } + + const auto& core_message = prepared->core_message; + core_request->api_version = core_message.nonce_values.api_version; + core_request->nonce = core_message.nonce_values.nonce; + core_request->session_id = core_message.nonce_values.session_id; + return core_message.message_type == message_type && + core_message.message_length == GetOffset(msg) && + core_request->api_version == CURRENT_OEC_VERSION; +} + +/** + * Template for parsing requests + * + * Template arguments: + * T: struct to be deserialized by odk + * S: kdo input struct + * P: auto-generated serializing function for |T| + */ +template +bool CreateResponse(uint32_t message_type, const S& core_request, + string* oemcrypto_core_message, T& response, + const P& packer) { + if (!oemcrypto_core_message) { + return false; + } + + auto* header = reinterpret_cast(&response); + header->message_type = message_type; + header->nonce_values.api_version = core_request.api_version; + header->nonce_values.nonce = core_request.nonce; + header->nonce_values.session_id = core_request.session_id; + + uint8_t buf[2048] = {0}; + Message* msg = NULL; + AllocateMessage(&msg, message_block); + InitMessage(msg, buf, sizeof(buf)); + packer(msg, &response); + if (!ValidMessage(msg)) { + return false; + } + + uint32_t message_length = GetSize(msg); + InitMessage(msg, buf + sizeof(header->message_type), + sizeof(header->message_length)); + Pack_uint32_t(msg, &message_length); + oemcrypto_core_message->assign(reinterpret_cast(buf), + message_length); + return true; +} + +bool CopyDeviceId(ODK_ProvisioningResponse& dest, + const ODK_ProvisioningRequest& src) { + auto& core_provisioning = dest.core_provisioning; + const string& device_id = src.device_id; + core_provisioning.device_id_length = device_id.size(); + if (core_provisioning.device_id_length > + sizeof(core_provisioning.device_id)) { + return false; + } + memset(core_provisioning.device_id, 0, sizeof(core_provisioning.device_id)); + memcpy(core_provisioning.device_id, device_id.data(), + core_provisioning.device_id_length); + return true; +} + +} // namespace + +// @ public parse request (deserializer) functions + +bool ParseLicenseRequest(const string& oemcrypto_core_message, + ODK_LicenseRequest* core_license_request) { + const auto unpacker = Unpack_ODK_PreparedLicense; + ODK_PreparedLicense prepared_license = {}; + return ParseRequest(ODK_License_Request_Type, oemcrypto_core_message, + core_license_request, &prepared_license, unpacker); +} + +bool ParseRenewalRequest(const string& oemcrypto_core_message, + ODK_RenewalRequest* core_renewal_request) { + const auto unpacker = Unpack_ODK_RenewalMessage; + ODK_RenewalMessage prepared_renewal = {}; + if (!ParseRequest(ODK_Renewal_Request_Type, oemcrypto_core_message, + core_renewal_request, &prepared_renewal, unpacker)) { + return false; + } + core_renewal_request->playback_time = prepared_renewal.playback_time; + return true; +} + +bool ParseProvisioningRequest( + const string& oemcrypto_core_message, + ODK_ProvisioningRequest* core_provisioning_request) { + const auto unpacker = Unpack_ODK_ProvisioningMessage; + ODK_ProvisioningMessage prepared_provision = {}; + if (!ParseRequest(ODK_Provisioning_Request_Type, oemcrypto_core_message, + core_provisioning_request, &prepared_provision, unpacker)) { + return false; + } + const uint8_t* device_id = prepared_provision.device_id; + const uint32_t device_id_length = prepared_provision.device_id_length; + if (device_id_length > ODK_DEVICE_ID_LEN_MAX) { + return false; + } + uint8_t zero[ODK_DEVICE_ID_LEN_MAX] = {}; + if (memcmp(zero, device_id + device_id_length, + ODK_DEVICE_ID_LEN_MAX - device_id_length)) { + return false; + } + core_provisioning_request->device_id.assign( + reinterpret_cast(device_id), device_id_length); + return true; +} + +// @ public create response functions + +bool CreateCoreLicenseResponse(const ODK_ParsedLicense& parsed_lic, + const ODK_LicenseRequest& core_request, + string* oemcrypto_core_message) { + ODK_LicenseResponse license_response{ + {}, const_cast(&parsed_lic)}; + return CreateResponse(ODK_License_Response_Type, core_request, + oemcrypto_core_message, license_response, + Pack_ODK_LicenseResponse); +} + +bool CreateCoreRenewalResponse(const ODK_RenewalRequest& core_request, + string* oemcrypto_core_message) { + ODK_RenewalMessage renewal{{}, core_request.playback_time}; + renewal.playback_time = core_request.playback_time; + return CreateResponse(ODK_Renewal_Response_Type, core_request, + oemcrypto_core_message, renewal, + Pack_ODK_RenewalMessage); +} + +bool CreateCoreProvisioningResponse(const ODK_ParsedProvisioning& parsed_prov, + const ODK_ProvisioningRequest& core_request, + string* oemcrypto_core_message) { + ODK_ProvisioningResponse prov_response{ + {}, const_cast(&parsed_prov)}; + if (!CopyDeviceId(prov_response, core_request)) { + return false; + } + + return CreateResponse(ODK_Provisioning_Response_Type, core_request, + oemcrypto_core_message, prov_response, + Pack_ODK_ProvisioningResponse); +} + +} // namespace oec_util diff --git a/libwvdrmengine/oemcrypto/odk/kdo/src/oec_util_proto.cpp b/libwvdrmengine/oemcrypto/odk/kdo/src/oec_util_proto.cpp new file mode 100644 index 00000000..02474d2b --- /dev/null +++ b/libwvdrmengine/oemcrypto/odk/kdo/src/oec_util_proto.cpp @@ -0,0 +1,161 @@ +/* + * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary + * source code may only be used and distributed under the Widevine Master + * License Agreement. + */ + +#include "oec_util_proto.h" + +#include +#include +#include +#include +#include +#include + +#include "odk_overflow.h" +#include "odk_serialize.h" +#include "odk_structs.h" +#include "odk_structs_priv.h" +#include "oemcrypto_types.h" +#include "serialization_base.h" + +using namespace oec_util; + +namespace oec_util { + +namespace { + +/* @ private functions */ + +/** + * Extract OEMCrypto_Substring (offset, length) from serialized protobuf + * + * Parameters: + * message: serialized license protobuf + * field: substring value + */ +OEMCrypto_Substring GetOecSubstring(const std::string& message, + const std::string& field) { + OEMCrypto_Substring substring = {}; + size_t pos = message.find(field); + if (pos != std::string::npos) { + substring = OEMCrypto_Substring{pos, field.length()}; + } + return substring; +} + +OEMCrypto_KeyObject KeyContainerToOecKey(const string& proto, + const License::KeyContainer& k) { + OEMCrypto_KeyObject obj = {}; + obj.key_id = GetOecSubstring(proto, k.id()); + obj.key_data_iv = GetOecSubstring(proto, k.iv()); + // Strip off PKCS#5 padding - since we know the key is 16 or 32 bytes, + // the padding will always be 16 bytes. + const string& key_data = k.key(); + const size_t PKCS5_PADDING_SIZE = 16; + obj.key_data = GetOecSubstring( + proto, key_data.substr(0, std::max(PKCS5_PADDING_SIZE, key_data.size()) - + PKCS5_PADDING_SIZE)); + if (k.has_key_control()) { + const auto& key_control = k.key_control(); + obj.key_control_iv = GetOecSubstring(proto, key_control.iv()); + obj.key_control = GetOecSubstring(proto, key_control.key_control_block()); + } + return obj; +} + +} // namespace + +// @ public create response functions + +bool CreateCoreLicenseResponse(const video_widevine::License& lic, + const ODK_LicenseRequest& core_request, + string* oemcrypto_core_message) { + string proto; + if (!lic.SerializeToString(&proto)) { + return false; + } + ODK_ParsedLicense parsed_lic{}; + + for (int i = 0; i < lic.key_size(); ++i) { + const auto& k = lic.key(i); + switch (k.type()) { + case License_KeyContainer::SIGNING: { + parsed_lic.enc_mac_keys_iv = GetOecSubstring(proto, k.iv()); + // Strip off PKCS#5 padding + string mac_keys(k.key(), 2 * wvoec::MAC_KEY_SIZE); + parsed_lic.enc_mac_keys = GetOecSubstring(proto, mac_keys); + break; + } + case License_KeyContainer::CONTENT: { + if (parsed_lic.key_array_length >= ODK_MAX_NUM_KEYS) { + return false; + } + uint32_t& n = parsed_lic.key_array_length; + parsed_lic.key_array[n++] = KeyContainerToOecKey(proto, k); + break; + } + default: { + continue; + } + } + } + + const auto& license_id = lic.id(); + if (license_id.has_provider_session_token()) { + parsed_lic.pst = + GetOecSubstring(proto, license_id.provider_session_token()); + } + + if (lic.has_srm_requirement()) { + parsed_lic.srm_restriction_data = + GetOecSubstring(proto, lic.srm_requirement()); + } + + parsed_lic.license_type = license_id.type(); + // todo: nonce_required + const auto& policy = lic.policy(); + ODK_TimerLimits& timer_limits = parsed_lic.timer_limits; + timer_limits.soft_expiry = policy.soft_enforce_playback_duration(); + timer_limits.earliest_playback_start_seconds = 0; + timer_limits.latest_playback_start_seconds = + policy.license_duration_seconds(); + timer_limits.initial_playback_duration_seconds = + policy.playback_duration_seconds(); + timer_limits.renewal_playback_duration_seconds = + policy.playback_duration_seconds(); + timer_limits.license_duration_seconds = policy.license_duration_seconds(); + + return CreateCoreLicenseResponse(parsed_lic, core_request, + oemcrypto_core_message); +} + +bool CreateCoreProvisioningResponse( + const video_widevine::ProvisioningResponse& prov, + const ODK_ProvisioningRequest& core_request, + string* oemcrypto_core_message) { + ODK_ParsedProvisioning parsed_prov{}; + string proto; + if (!prov.SerializeToString(&proto)) { + return false; + } + + parsed_prov.key_type = 0; // todo: ECC or RSA + if (prov.has_device_rsa_key()) { + parsed_prov.enc_private_key = GetOecSubstring(proto, prov.device_rsa_key()); + } + if (prov.has_device_rsa_key_iv()) { + parsed_prov.enc_private_key_iv = + GetOecSubstring(proto, prov.device_rsa_key_iv()); + } + if (prov.has_wrapping_key()) { + parsed_prov.encrypted_message_key = + GetOecSubstring(proto, prov.wrapping_key()); + } + + return CreateCoreProvisioningResponse(parsed_prov, core_request, + oemcrypto_core_message); +} + +} // namespace oec_util diff --git a/libwvdrmengine/oemcrypto/odk/src/odk.c b/libwvdrmengine/oemcrypto/odk/src/odk.c new file mode 100644 index 00000000..92cb8ca4 --- /dev/null +++ b/libwvdrmengine/oemcrypto/odk/src/odk.c @@ -0,0 +1,316 @@ +/* + * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary + * source code may only be used and distributed under the Widevine Master + * License Agreement. + */ + +#include +#include +#include + +#include "odk.h" +#include "odk_overflow.h" +#include "odk_serialize.h" +#include "odk_structs.h" +#include "odk_structs_priv.h" +#include "serialization_base.h" + +#define ODK_LICENSE_REQUEST_SIZE 20 +#define ODK_RENEWAL_REQUEST_SIZE 28 +#define ODK_PROVISIONING_REQUEST_SIZE 88 + +/* @ private odk functions */ + +static OEMCryptoResult ODK_PrepareRequest(uint8_t* buffer, size_t buffer_length, + size_t* core_message_length, + uint32_t message_type, + const ODK_NonceValues* nonce_values, + ODK_CoreMessage* core_message) { + if (!nonce_values || !core_message_length || !core_message || + *core_message_length > buffer_length) { + return ODK_ERROR_CORE_MESSAGE; + } + + Message* msg = NULL; + AllocateMessage(&msg, message_block); + InitMessage(msg, buffer, *core_message_length); + *core_message = (ODK_CoreMessage){ + message_type, 0, *nonce_values, + }; + + switch (message_type) { + case ODK_License_Request_Type: { + core_message->message_length = ODK_LICENSE_REQUEST_SIZE; + Pack_ODK_PreparedLicense(msg, (ODK_PreparedLicense*)core_message); + break; + } + case ODK_Renewal_Request_Type: { + core_message->message_length = ODK_RENEWAL_REQUEST_SIZE; + Pack_ODK_RenewalMessage(msg, (ODK_RenewalMessage*)core_message); + break; + } + case ODK_Provisioning_Request_Type: { + core_message->message_length = ODK_PROVISIONING_REQUEST_SIZE; + Pack_ODK_ProvisioningMessage(msg, (ODK_ProvisioningMessage*)core_message); + break; + } + default: { + return ODK_ERROR_CORE_MESSAGE; + } + } + + *core_message_length = core_message->message_length; + if (GetStatus(msg) != MESSAGE_STATUS_OK || + GetSize(msg) != *core_message_length) { + return OEMCrypto_ERROR_SHORT_BUFFER; + } + return OEMCrypto_SUCCESS; +} + +static OEMCryptoResult ODK_ParseResponse(const uint8_t* buf, + size_t message_length, + uint32_t message_type, + const ODK_NonceValues* nonce_values, + ODK_CoreMessage* const core_message) { + Message* msg = NULL; + AllocateMessage(&msg, message_block); + InitMessage(msg, (uint8_t*)buf, message_length); + SetSize(msg, message_length); + + switch (message_type) { + case ODK_License_Response_Type: { + Unpack_ODK_LicenseResponse(msg, (ODK_LicenseResponse*)core_message); + break; + } + case ODK_Renewal_Response_Type: { + Unpack_ODK_RenewalMessage(msg, (ODK_RenewalMessage*)core_message); + break; + } + case ODK_Provisioning_Response_Type: { + Unpack_ODK_ProvisioningResponse(msg, + (ODK_ProvisioningResponse*)core_message); + break; + } + default: { + return ODK_ERROR_CORE_MESSAGE; + } + } + + if (GetStatus(msg) != MESSAGE_STATUS_OK || + message_type != core_message->message_type || + GetOffset(msg) != core_message->message_length) { + return ODK_ERROR_CORE_MESSAGE; + } + + if (nonce_values) { + /* always verify nonce_values for Renewal and Provisioning responses */ + if (nonce_values->api_version != core_message->nonce_values.api_version || + nonce_values->nonce != core_message->nonce_values.nonce || + nonce_values->session_id != core_message->nonce_values.session_id) { + return OEMCrypto_ERROR_INVALID_NONCE; + } + } + + return OEMCrypto_SUCCESS; +} + +/* @ public odk functions */ + +/* @@ prepare request functions */ + +OEMCryptoResult ODK_PrepareCoreLicenseRequest( + uint8_t* message, size_t message_length, size_t* core_message_length, + const ODK_NonceValues* nonce_values) { + ODK_PreparedLicense license_request = {0}; + return ODK_PrepareRequest(message, message_length, core_message_length, + ODK_License_Request_Type, nonce_values, + &license_request.core_message); +} + +OEMCryptoResult ODK_PrepareCoreRenewalRequest( + uint8_t* message, size_t message_length, size_t* core_message_length, + const ODK_NonceValues* nonce_values, + const ODK_ClockValues* clock_values, uint64_t system_time_seconds) { + ODK_RenewalMessage renewal_request = { + {0}, + }; + if (odk_sub_overflow_u64(system_time_seconds, + clock_values->time_of_first_decrypt, + &renewal_request.playback_time)) { + return ODK_ERROR_CORE_MESSAGE; + } + return ODK_PrepareRequest(message, message_length, core_message_length, + ODK_Renewal_Request_Type, nonce_values, + &renewal_request.core_message); +} + +OEMCryptoResult ODK_PrepareCoreProvisioningRequest( + uint8_t* message, size_t message_length, size_t* core_message_length, + const ODK_NonceValues* nonce_values, + const uint8_t* device_id, size_t device_id_length) { + ODK_ProvisioningMessage provisioning_request = { + {0}, + }; + if (device_id_length > sizeof(provisioning_request.device_id)) { + return ODK_ERROR_CORE_MESSAGE; + } + provisioning_request.device_id_length = device_id_length; + if (device_id) { + memcpy(provisioning_request.device_id, device_id, device_id_length); + } + return ODK_PrepareRequest(message, message_length, core_message_length, + ODK_Provisioning_Request_Type, nonce_values, + &provisioning_request.core_message); +} + +/* @@ parse request functions */ + +OEMCryptoResult ODK_ParseLicense(const uint8_t* message, size_t message_length, + size_t core_message_length, + bool initial_license_load, + bool usage_entry_present, + const uint8_t* request_hash, + ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + ODK_NonceValues* nonce_values, + ODK_ParsedLicense* parsed_license) { + + if (!nonce_values || !parsed_license) { + return ODK_ERROR_CORE_MESSAGE; + } + + ODK_LicenseResponse license_response = {{0}, parsed_license}; + OEMCryptoResult err = ODK_ParseResponse( + message, message_length, ODK_License_Response_Type, NULL, + &license_response.core_message); + + if (err != OEMCrypto_SUCCESS) { + return err; + } + + if (license_response.core_message.nonce_values.api_version != 16) { + return ODK_UNSUPPORTED_API; + } + + if (parsed_license->nonce_required) { + if (initial_license_load) { + if (nonce_values->nonce != license_response.core_message.nonce_values.nonce || + nonce_values->session_id != license_response.core_message.nonce_values.session_id) { + return OEMCrypto_ERROR_INVALID_NONCE; + } + } else { /* !initial_license_load */ + nonce_values->nonce = license_response.core_message.nonce_values.nonce; + nonce_values->session_id = license_response.core_message.nonce_values.session_id; + } + } + + if (initial_license_load && + memcmp(request_hash, parsed_license->request_hash, ODK_SHA256_HASH_SIZE)) { + return ODK_ERROR_CORE_MESSAGE; + } + + if (usage_entry_present && parsed_license->pst.length == 0) { + return ODK_ERROR_CORE_MESSAGE; + } + + return err; +} + +OEMCryptoResult ODK_ParseRenewal(const uint8_t* message, size_t message_length, + size_t core_message_length, + const ODK_NonceValues* nonce_values, + uint64_t system_time, + const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + uint64_t* timer_value) { + if (!nonce_values || !timer_limits || !clock_values || !timer_value) { + return ODK_ERROR_CORE_MESSAGE; + } + + ODK_RenewalMessage renewal_response = { + {0}, + }; + OEMCryptoResult err = ODK_ParseResponse( + message, message_length, ODK_Renewal_Response_Type, nonce_values, + &renewal_response.core_message); + + if (err) { + return err; + } + + /* Reference: + * Doc: License Duration and Renewal (Changes for OEMCrypto v16) + * Section: Renewal Message + */ + + uint64_t playback_timer = 0; + if (odk_sub_overflow_u64(clock_values->time_when_timer_expires, system_time, + &playback_timer)) { + return ODK_TIMER_EXPIRED; + } + + uint64_t time_since_playback_began = 0; + uint64_t time_since_reset = 0; + uint64_t time_since_message_signed = 0; + /* ... or use clock_values->time_of_license_signed ? */ + if (odk_sub_overflow_u64(system_time, clock_values->time_of_first_decrypt, + &time_since_playback_began) || + odk_sub_overflow_u64(timer_limits->renewal_playback_duration_seconds, + playback_timer, &time_since_reset) || + odk_sub_overflow_u64(time_since_playback_began, + renewal_response.playback_time, + &time_since_message_signed) || + time_since_message_signed >= time_since_reset || + odk_add_overflow_u64(system_time, + timer_limits->renewal_playback_duration_seconds, + &clock_values->time_when_timer_expires)) { + return ODK_ERROR_CORE_MESSAGE; + } + + /* todo: when to return ODK_DISABLE_TIMER */ + *timer_value = timer_limits->renewal_playback_duration_seconds; + return ODK_SET_TIMER; +} + +OEMCryptoResult ODK_ParseProvisioning( + const uint8_t* message, size_t message_length, + size_t core_message_length, + const ODK_NonceValues* nonce_values, + const uint8_t* device_id, + size_t device_id_length, ODK_ParsedProvisioning* parsed_response) { + if (!nonce_values || !device_id || !parsed_response) { + return ODK_ERROR_CORE_MESSAGE; + } + + ODK_ProvisioningResponse provisioning_response = {{ + {0}, + }, + parsed_response}; + if (device_id_length > ODK_DEVICE_ID_LEN_MAX) { + return ODK_ERROR_CORE_MESSAGE; + } + + OEMCryptoResult err = ODK_ParseResponse( + message, message_length, ODK_Provisioning_Response_Type, + nonce_values, &provisioning_response.core_provisioning.core_message); + + if (err) { + return err; + } + + if (memcmp(device_id, provisioning_response.core_provisioning.device_id, + device_id_length)) { + return ODK_ERROR_CORE_MESSAGE; + } + + uint8_t zero[ODK_DEVICE_ID_LEN_MAX] = {0}; + /* check bytes beyond device_id_length are 0 */ + if (memcmp( + zero, + provisioning_response.core_provisioning.device_id + device_id_length, + ODK_DEVICE_ID_LEN_MAX - device_id_length)) { + return ODK_ERROR_CORE_MESSAGE; + } + + return OEMCrypto_SUCCESS; +} diff --git a/libwvdrmengine/oemcrypto/odk/src/odk_overflow.c b/libwvdrmengine/oemcrypto/odk/src/odk_overflow.c new file mode 100644 index 00000000..3a05f222 --- /dev/null +++ b/libwvdrmengine/oemcrypto/odk/src/odk_overflow.c @@ -0,0 +1,38 @@ +/* + * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary + * source code may only be used and distributed under the Widevine Master + * License Agreement. + */ + +#include +#include + +int odk_sub_overflow_u64(uint64_t a, uint64_t b, uint64_t* c) { + if (a >= b) { + if (c) { + *c = a - b; + } + return 0; + } + return 1; +} + +int odk_add_overflow_u64(uint64_t a, uint64_t b, uint64_t* c) { + if (UINT64_MAX - a >= b) { + if (c) { + *c = a + b; + } + return 0; + } + return 1; +} + +int odk_add_overflow_ux(size_t a, size_t b, size_t* c) { + if (SIZE_MAX - a >= b) { + if (c) { + *c = a + b; + } + return 0; + } + return 1; +} diff --git a/libwvdrmengine/oemcrypto/odk/src/odk_serialize.c b/libwvdrmengine/oemcrypto/odk/src/odk_serialize.c new file mode 100644 index 00000000..dab42be6 --- /dev/null +++ b/libwvdrmengine/oemcrypto/odk/src/odk_serialize.c @@ -0,0 +1,209 @@ +/* + * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary + * source code may only be used and distributed under the Widevine Master + * License Agreement. + */ + +/* + * This code is auto-generated, do not edit + */ + +#include "odk_structs_priv.h" +#include "serialization_base.h" + +/* @ serialize */ + +/* @@ private serialize */ + +static void Pack_ODK_NonceValues(Message* msg, ODK_NonceValues const* obj) { + Pack_uint32_t(msg, &obj->api_version); + Pack_uint32_t(msg, &obj->nonce); + Pack_uint32_t(msg, &obj->session_id); +} + +static void Pack_ODK_CoreMessage(Message* msg, ODK_CoreMessage const* obj) { + Pack_uint32_t(msg, &obj->message_type); + Pack_uint32_t(msg, &obj->message_length); + Pack_ODK_NonceValues(msg, &obj->nonce_values); +} + +static void Pack_OEMCrypto_KeyObject(Message* msg, + OEMCrypto_KeyObject const* obj) { + Pack_OEMCrypto_Substring(msg, (const OEMCrypto_Substring*)&obj->key_id); + Pack_OEMCrypto_Substring(msg, (const OEMCrypto_Substring*)&obj->key_data_iv); + Pack_OEMCrypto_Substring(msg, (const OEMCrypto_Substring*)&obj->key_data); + Pack_OEMCrypto_Substring(msg, + (const OEMCrypto_Substring*)&obj->key_control_iv); + Pack_OEMCrypto_Substring(msg, (const OEMCrypto_Substring*)&obj->key_control); +} + +static void Pack_ODK_TimerLimits(Message* msg, ODK_TimerLimits const* obj) { + Pack_uint32_t(msg, (const uint32_t*)&obj->soft_expiry); + Pack_uint64_t(msg, (const uint64_t*)&obj->earliest_playback_start_seconds); + Pack_uint64_t(msg, (const uint64_t*)&obj->latest_playback_start_seconds); + Pack_uint64_t(msg, (const uint64_t*)&obj->initial_playback_duration_seconds); + Pack_uint64_t(msg, (const uint64_t*)&obj->renewal_playback_duration_seconds); + Pack_uint64_t(msg, (const uint64_t*)&obj->license_duration_seconds); +} + +static void Pack_ODK_ParsedLicense(Message* msg, ODK_ParsedLicense const* obj) { + /* hand-coded */ + if (obj->key_array_length > ODK_MAX_NUM_KEYS) { + SetStatus(msg, MESSAGE_STATUS_OVERFLOW_ERROR); + return; + } + Pack_OEMCrypto_Substring(msg, + (const OEMCrypto_Substring*)&obj->enc_mac_keys_iv); + Pack_OEMCrypto_Substring(msg, (const OEMCrypto_Substring*)&obj->enc_mac_keys); + Pack_OEMCrypto_Substring(msg, (const OEMCrypto_Substring*)&obj->pst); + Pack_OEMCrypto_Substring( + msg, (const OEMCrypto_Substring*)&obj->srm_restriction_data); + Pack_uint32_t(msg, (const uint32_t*)&obj->license_type); + Pack_uint32_t(msg, (const uint32_t*)&obj->nonce_required); + Pack_ODK_TimerLimits(msg, (const ODK_TimerLimits*)&obj->timer_limits); + PackArray(msg, (const uint8_t*)&obj->request_hash[0], sizeof(obj->request_hash)); + Pack_uint32_t(msg, (const uint32_t*)&obj->key_array_length); + for (size_t i = 0; i < (size_t)obj->key_array_length; i++) { + Pack_OEMCrypto_KeyObject(msg, &obj->key_array[i]); + } +} + +static void Pack_ODK_ParsedProvisioning(Message* msg, + ODK_ParsedProvisioning const* obj) { + Pack_uint32_t(msg, (const uint32_t*)&obj->key_type); + Pack_OEMCrypto_Substring(msg, + (const OEMCrypto_Substring*)&obj->enc_private_key); + Pack_OEMCrypto_Substring( + msg, (const OEMCrypto_Substring*)&obj->enc_private_key_iv); + Pack_OEMCrypto_Substring( + msg, (const OEMCrypto_Substring*)&obj->encrypted_message_key); +} + +/* @@ odk serialize */ + +void Pack_ODK_PreparedLicense(Message* msg, ODK_PreparedLicense const* obj) { + Pack_ODK_CoreMessage(msg, (const ODK_CoreMessage*)&obj->core_message); +} + +void Pack_ODK_RenewalMessage(Message* msg, ODK_RenewalMessage const* obj) { + Pack_ODK_CoreMessage(msg, (const ODK_CoreMessage*)&obj->core_message); + Pack_uint64_t(msg, (const uint64_t*)&obj->playback_time); +} + +void Pack_ODK_ProvisioningMessage(Message* msg, + ODK_ProvisioningMessage const* obj) { + Pack_ODK_CoreMessage(msg, (const ODK_CoreMessage*)&obj->core_message); + Pack_uint32_t(msg, (const uint32_t*)&obj->device_id_length); + PackArray(msg, (const uint8_t*)&obj->device_id[0], sizeof(obj->device_id)); +} + +/* @@ kdo serialize */ + +void Pack_ODK_LicenseResponse(Message* msg, ODK_LicenseResponse const* obj) { + Pack_ODK_CoreMessage(msg, (const ODK_CoreMessage*)&obj->core_message); + Pack_ODK_ParsedLicense(msg, (const ODK_ParsedLicense*)obj->parsed_license); +} + +void Pack_ODK_ProvisioningResponse(Message* msg, + ODK_ProvisioningResponse const* obj) { + Pack_ODK_ProvisioningMessage( + msg, (const ODK_ProvisioningMessage*)&obj->core_provisioning); + Pack_ODK_ParsedProvisioning( + msg, (const ODK_ParsedProvisioning*)obj->parsed_provisioning); +} + +/* @ deserialize */ + +/* @@ private deserialize */ + +static void Unpack_ODK_NonceValues(Message* msg, ODK_NonceValues* obj) { + Unpack_uint32_t(msg, &obj->api_version); + Unpack_uint32_t(msg, &obj->nonce); + Unpack_uint32_t(msg, &obj->session_id); +} + +static void Unpack_ODK_CoreMessage(Message* msg, ODK_CoreMessage* obj) { + Unpack_uint32_t(msg, &obj->message_type); + Unpack_uint32_t(msg, &obj->message_length); + Unpack_ODK_NonceValues(msg, &obj->nonce_values); +} + +static void Unpack_OEMCrypto_KeyObject(Message* msg, OEMCrypto_KeyObject* obj) { + Unpack_OEMCrypto_Substring(msg, (OEMCrypto_Substring*)&obj->key_id); + Unpack_OEMCrypto_Substring(msg, (OEMCrypto_Substring*)&obj->key_data_iv); + Unpack_OEMCrypto_Substring(msg, (OEMCrypto_Substring*)&obj->key_data); + Unpack_OEMCrypto_Substring(msg, (OEMCrypto_Substring*)&obj->key_control_iv); + Unpack_OEMCrypto_Substring(msg, (OEMCrypto_Substring*)&obj->key_control); +} + +static void Unpack_ODK_TimerLimits(Message* msg, ODK_TimerLimits* obj) { + Unpack_uint32_t(msg, (uint32_t*)&obj->soft_expiry); + Unpack_uint64_t(msg, (uint64_t*)&obj->earliest_playback_start_seconds); + Unpack_uint64_t(msg, (uint64_t*)&obj->latest_playback_start_seconds); + Unpack_uint64_t(msg, (uint64_t*)&obj->initial_playback_duration_seconds); + Unpack_uint64_t(msg, (uint64_t*)&obj->renewal_playback_duration_seconds); + Unpack_uint64_t(msg, (uint64_t*)&obj->license_duration_seconds); +} + +static void Unpack_ODK_ParsedLicense(Message* msg, ODK_ParsedLicense* obj) { + Unpack_OEMCrypto_Substring(msg, (OEMCrypto_Substring*)&obj->enc_mac_keys_iv); + Unpack_OEMCrypto_Substring(msg, (OEMCrypto_Substring*)&obj->enc_mac_keys); + Unpack_OEMCrypto_Substring(msg, (OEMCrypto_Substring*)&obj->pst); + Unpack_OEMCrypto_Substring(msg, + (OEMCrypto_Substring*)&obj->srm_restriction_data); + Unpack_uint32_t(msg, (uint32_t*)&obj->license_type); + Unpack_uint32_t(msg, (uint32_t*)&obj->nonce_required); + Unpack_ODK_TimerLimits(msg, (ODK_TimerLimits*)&obj->timer_limits); + UnpackArray(msg, (uint8_t*)&obj->request_hash[0], sizeof(obj->request_hash)); + Unpack_uint32_t(msg, (uint32_t*)&obj->key_array_length); + if (obj->key_array_length > ODK_MAX_NUM_KEYS) { + SetStatus(msg, MESSAGE_STATUS_OVERFLOW_ERROR); + return; + } + for (uint32_t i = 0; i < obj->key_array_length; i++) { + Unpack_OEMCrypto_KeyObject(msg, &obj->key_array[i]); + } +} + +static void Unpack_ODK_ParsedProvisioning(Message* msg, + ODK_ParsedProvisioning* obj) { + Unpack_uint32_t(msg, (uint32_t*)&obj->key_type); + Unpack_OEMCrypto_Substring(msg, (OEMCrypto_Substring*)&obj->enc_private_key); + Unpack_OEMCrypto_Substring(msg, + (OEMCrypto_Substring*)&obj->enc_private_key_iv); + Unpack_OEMCrypto_Substring(msg, + (OEMCrypto_Substring*)&obj->encrypted_message_key); +} + +/* @ kdo deserialize */ + +void Unpack_ODK_PreparedLicense(Message* msg, ODK_PreparedLicense* obj) { + Unpack_ODK_CoreMessage(msg, (ODK_CoreMessage*)&obj->core_message); +} + +void Unpack_ODK_ProvisioningMessage(Message* msg, + ODK_ProvisioningMessage* obj) { + Unpack_ODK_CoreMessage(msg, (ODK_CoreMessage*)&obj->core_message); + Unpack_uint32_t(msg, (uint32_t*)&obj->device_id_length); + UnpackArray(msg, (uint8_t*)&obj->device_id[0], sizeof(obj->device_id)); +} + +/* @@ odk deserialize */ + +void Unpack_ODK_LicenseResponse(Message* msg, ODK_LicenseResponse* obj) { + Unpack_ODK_CoreMessage(msg, (ODK_CoreMessage*)&obj->core_message); + Unpack_ODK_ParsedLicense(msg, (ODK_ParsedLicense*)obj->parsed_license); +} + +void Unpack_ODK_RenewalMessage(Message* msg, ODK_RenewalMessage* obj) { + Unpack_ODK_CoreMessage(msg, (ODK_CoreMessage*)&obj->core_message); + Unpack_uint64_t(msg, (uint64_t*)&obj->playback_time); +} + +void Unpack_ODK_ProvisioningResponse(Message* msg, + ODK_ProvisioningResponse* obj) { + Unpack_ODK_ProvisioningMessage( + msg, (ODK_ProvisioningMessage*)&obj->core_provisioning); + Unpack_ODK_ParsedProvisioning( + msg, (ODK_ParsedProvisioning*)obj->parsed_provisioning); +} diff --git a/libwvdrmengine/oemcrypto/odk/src/odk_timer.c b/libwvdrmengine/oemcrypto/odk/src/odk_timer.c new file mode 100644 index 00000000..e0ae2bfa --- /dev/null +++ b/libwvdrmengine/oemcrypto/odk/src/odk_timer.c @@ -0,0 +1,229 @@ +/* + * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary + * source code may only be used and distributed under the Widevine Master + * License Agreement. + */ + +#include +#include + +#include "odk.h" + +OEMCryptoResult ODK_InitializeSessionValues(ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + ODK_NonceValues* nonce_values, + uint32_t api_version, + uint32_t session_id) { + if (clock_values == NULL || clock_values == NULL || nonce_values == NULL) + return OEMCrypto_ERROR_INVALID_CONTEXT; + timer_limits->soft_expiry = false; + timer_limits->earliest_playback_start_seconds = 0; + timer_limits->latest_playback_start_seconds = 0; + timer_limits->initial_playback_duration_seconds = 0; + timer_limits->renewal_playback_duration_seconds = 0; + timer_limits->license_duration_seconds = 0; + + clock_values->time_of_license_signed = 0; + clock_values->time_of_first_decrypt = 0; + clock_values->time_of_last_decrypt = 0; + clock_values->time_when_timer_expires = 0; + clock_values->timer_status = 0; + clock_values->status = kUnused; + + nonce_values->api_version = api_version; + nonce_values->nonce = 0; + nonce_values->session_id = session_id; + + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult ODK_SetNonceValues(ODK_NonceValues* nonce_values, + uint32_t nonce) { + nonce_values->nonce = nonce; + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult ODK_InitializeClockValues(ODK_ClockValues* clock_values, + uint64_t system_time_seconds) { + if (clock_values == NULL) return OEMCrypto_ERROR_INVALID_CONTEXT; + clock_values->time_of_license_signed = system_time_seconds; + clock_values->time_of_first_decrypt = 0; + clock_values->time_of_last_decrypt = 0; + clock_values->time_when_timer_expires = 0; + /* TODO(b/142415188): document this. */ + clock_values->timer_status = 0; + clock_values->status = kUnused; + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult ODK_ReloadClockValues(ODK_ClockValues* clock_values, + uint64_t time_of_license_signed, + uint64_t time_of_first_decrypt, + uint64_t time_of_last_decrypt, + enum OEMCrypto_Usage_Entry_Status status, + uint64_t system_time_seconds) { + if (clock_values == NULL) return OEMCrypto_ERROR_INVALID_CONTEXT; + clock_values->time_of_license_signed = time_of_license_signed; + clock_values->time_of_first_decrypt = time_of_first_decrypt; + clock_values->time_of_last_decrypt = time_of_last_decrypt; + clock_values->time_when_timer_expires = 0; + clock_values->timer_status = 0; + clock_values->status = status; + return OEMCrypto_SUCCESS; +} + +/* This is called on the first playback for a session. */ +uint32_t ODK_AttemptFirstPlayback(uint64_t system_time_seconds, + const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + uint64_t* timer_value) { + if (clock_values == NULL || timer_limits == NULL) + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + /* All times are relative to when the license was signed. */ + const uint64_t rental_time = + system_time_seconds - clock_values->time_of_license_signed; + if (rental_time < timer_limits->earliest_playback_start_seconds) { + clock_values->timer_status = ODK_TIMER_EXPIRED; + return ODK_TIMER_EXPIRED; + } + /* If the clock status is already marked as inactive, then playback is + * not allowed. */ + /* TODO(b/142415188): add helper function. */ + if (clock_values->status > kActive) { + clock_values->timer_status = ODK_TIMER_EXPIRED; + return ODK_TIMER_EXPIRED; + } + /* If this license is still inactive (never used) then we just look at the + * rental window. This is the first playback for the license, not just this + * session. */ + if (clock_values->status == kUnused) { + /* If the rental clock has expired, the license has expired. */ + if (rental_time > timer_limits->latest_playback_start_seconds) { + clock_values->timer_status = ODK_TIMER_EXPIRED; + return ODK_TIMER_EXPIRED; + } + /* The timer should be limited by the playback duration. */ + uint64_t time_left = timer_limits->initial_playback_duration_seconds; + /* If there is a license duration, it also limits the timer. Remeber, a + * limit of 0 means no limit, or infinite. */ + if (timer_limits->license_duration_seconds > 0) { + if (timer_limits->license_duration_seconds < rental_time) { + /* If the license duration has expired. This is unusual, because this + * can only happen if the license duration is less than the rental + * window. */ + clock_values->timer_status = ODK_TIMER_EXPIRED; + return ODK_TIMER_EXPIRED; + } + if (timer_limits->license_duration_seconds - rental_time < time_left || + time_left == 0) { + time_left = timer_limits->license_duration_seconds - rental_time; + } + } + /* This is a new license, and we can start playback. */ + clock_values->status = kActive; + clock_values->time_of_first_decrypt = system_time_seconds; + clock_values->time_of_last_decrypt = system_time_seconds; + if (time_left == 0 || timer_limits->soft_expiry) { /* Unlimited. */ + clock_values->time_when_timer_expires = 0; + clock_values->timer_status = ODK_DISABLE_TIMER; + return ODK_DISABLE_TIMER; + } + /* Set timer to limit playback. */ + if (timer_value) *timer_value = time_left; + clock_values->time_when_timer_expires = system_time_seconds + time_left; + clock_values->timer_status = ODK_SET_TIMER; + return ODK_SET_TIMER; + } + /* Otherwise, this is the second loading of a persistent license. In this + * case, we ignore the rental window. */ + const uint64_t time_since_first_decrypt = + system_time_seconds - clock_values->time_of_first_decrypt; + uint64_t time_left = 0; + /* If there is an initial playback duration, the we use that as a limit. + * This ignores any license renewals. If renewals are allowed, then the last + * one can be reloaded to reset the timer. */ + if (timer_limits->initial_playback_duration_seconds > 0) { + if (timer_limits->initial_playback_duration_seconds <= + time_since_first_decrypt) { + clock_values->timer_status = ODK_TIMER_EXPIRED; + return ODK_TIMER_EXPIRED; + } + time_left = timer_limits->initial_playback_duration_seconds - + time_since_first_decrypt; + } + /* If there is a license duration, it also limits the timer. */ + if (timer_limits->license_duration_seconds > 0) { + if (timer_limits->license_duration_seconds < rental_time) { + /* The license duration has expired. */ + clock_values->timer_status = ODK_TIMER_EXPIRED; + return ODK_TIMER_EXPIRED; + } + if (timer_limits->license_duration_seconds - rental_time < time_left || + time_left == 0) { + time_left = timer_limits->license_duration_seconds - rental_time; + } + } + /* We can restart playback for this license. Update last playback time. */ + clock_values->time_of_last_decrypt = system_time_seconds; + if (time_left == 0 || timer_limits->soft_expiry) { /* Unlimited. */ + clock_values->time_when_timer_expires = 0; + clock_values->timer_status = ODK_DISABLE_TIMER; + return ODK_DISABLE_TIMER; + } + /* Set timer. */ + if (timer_value) *timer_value = time_left; + clock_values->time_when_timer_expires = system_time_seconds + time_left; + clock_values->timer_status = ODK_SET_TIMER; + return ODK_SET_TIMER; +} + +/* This is called regularly during playback if OEMCrypto does not implement its + * own timer. */ +OEMCryptoResult ODK_UpdateLastPlaybackTime(uint64_t system_time_seconds, + const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values) { + if (clock_values == NULL || timer_limits == NULL) + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + if (clock_values->timer_status == ODK_TIMER_EXPIRED) { + return ODK_TIMER_EXPIRED; + } + if (clock_values->time_when_timer_expires > 0 && + system_time_seconds > clock_values->time_when_timer_expires) { + clock_values->timer_status = ODK_TIMER_EXPIRED; + return ODK_TIMER_EXPIRED; + } + clock_values->time_of_last_decrypt = system_time_seconds; + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult ODK_DeactivateUsageEntry(ODK_ClockValues* clock_values) { + if (clock_values == NULL) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + if (clock_values->status == kUnused) { + clock_values->status = kInactiveUnused; + } else { + clock_values->status = kInactiveUsed; + } + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult ODK_InitializeV15Values(ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + ODK_NonceValues* nonce_values, + uint32_t key_duration, + uint64_t system_time_seconds) { + if (clock_values == NULL || clock_values == NULL || nonce_values == NULL) + return OEMCrypto_ERROR_INVALID_CONTEXT; + timer_limits->soft_expiry = false; + timer_limits->earliest_playback_start_seconds = 0; + timer_limits->latest_playback_start_seconds = 0; + timer_limits->initial_playback_duration_seconds = key_duration; + timer_limits->renewal_playback_duration_seconds = key_duration; + timer_limits->license_duration_seconds = 0; + nonce_values->api_version = 15; + if (key_duration > 0) { + clock_values->time_when_timer_expires = system_time_seconds + key_duration; + } else { + clock_values->time_when_timer_expires = 0; + } + return OEMCrypto_SUCCESS; +} diff --git a/libwvdrmengine/oemcrypto/odk/src/serialization_base.c b/libwvdrmengine/oemcrypto/odk/src/serialization_base.c new file mode 100644 index 00000000..60cc473e --- /dev/null +++ b/libwvdrmengine/oemcrypto/odk/src/serialization_base.c @@ -0,0 +1,205 @@ +/* + * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary + * source code may only be used and distributed under the Widevine Master + * License Agreement. + */ + +#include "serialization_base.h" + +#include +#include +#include + +#include "OEMCryptoCENCCommon.h" +#include "odk_assert.h" +#include "odk_overflow.h" + +struct _Message { + uint8_t* base; + size_t capacity; + size_t size; /* bytes written */ + size_t read_offset; /* bytes read */ + MessageStatus status; +}; + +odk_static_assert(SIZE_OF_MESSAGE_STRUCT >= sizeof(Message), + "SIZE_OF_MESSAGE_STRUCT too small"); + +bool ValidMessage(Message* message) { + if (message == NULL) { + return false; + } + if (message->status != MESSAGE_STATUS_OK) { + return false; + } + if (message->base == NULL) { + message->status = MESSAGE_STATUS_NULL_POINTER_ERROR; + return false; + } + if (message->size > message->capacity || + message->read_offset > message->size) { + message->status = MESSAGE_STATUS_OVERFLOW_ERROR; + return false; + } + return true; +} + +static void PackBytes(Message* message, const uint8_t* ptr, size_t count) { + if (count <= message->capacity - message->size) { + memcpy((void*)(message->base + message->size), (void*)ptr, count); + message->size += count; + } else { + message->status = MESSAGE_STATUS_OVERFLOW_ERROR; + } +} + +void Pack_uint32_t(Message* message, const uint32_t* value) { + if (!ValidMessage(message)) return; + uint8_t data[4] = {0}; + data[0] = *value >> 24; + data[1] = *value >> 16; + data[2] = *value >> 8; + data[3] = *value >> 0; + PackBytes(message, data, sizeof(data)); +} + +void Pack_uint64_t(Message* message, const uint64_t* value) { + if (!ValidMessage(message)) return; + uint32_t hi = *value >> 32; + uint32_t lo = *value; + Pack_uint32_t(message, &hi); + Pack_uint32_t(message, &lo); +} + +void PackArray(Message* message, const uint8_t* base, size_t size) { + if (!ValidMessage(message)) return; + PackBytes(message, base, size); +} + +void Pack_OEMCrypto_Substring(Message* msg, const OEMCrypto_Substring* obj) { + uint32_t offset = obj->offset; + uint32_t length = obj->length; + Pack_uint32_t(msg, &offset); + Pack_uint32_t(msg, &length); +} + +static void UnpackBytes(Message* message, uint8_t* ptr, size_t count) { + if (count <= message->size - message->read_offset) { + memcpy((void*)ptr, (void*)(message->base + message->read_offset), count); + message->read_offset += count; + } else { + message->status = MESSAGE_STATUS_UNDERFLOW_ERROR; + } +} + +void Unpack_uint32_t(Message* message, uint32_t* value) { + if (!ValidMessage(message)) return; + uint8_t data[4] = {0}; + UnpackBytes(message, data, sizeof(data)); + *value = data[0]; + *value = *value << 8 | data[1]; + *value = *value << 8 | data[2]; + *value = *value << 8 | data[3]; +} + +void Unpack_uint64_t(Message* message, uint64_t* value) { + if (!ValidMessage(message)) return; + uint32_t hi = 0; + uint32_t lo = 0; + Unpack_uint32_t(message, &hi); + Unpack_uint32_t(message, &lo); + *value = hi; + *value = *value << 32 | lo; +} + +void Unpack_OEMCrypto_Substring(Message* msg, OEMCrypto_Substring* obj) { + uint32_t offset = 0, length = 0; + Unpack_uint32_t(msg, &offset); + Unpack_uint32_t(msg, &length); + if (!ValidMessage(msg)) return; + size_t end = 0; + if (offset > msg->capacity || odk_add_overflow_ux(offset, length, &end) || + end > msg->capacity) { + msg->status = MESSAGE_STATUS_OVERFLOW_ERROR; + return; + } + obj->offset = offset; + obj->length = length; +} + +/* copy out */ +void UnpackArray(Message* message, uint8_t* address, size_t size) { + if (!ValidMessage(message)) return; + UnpackBytes(message, address, size); +} + +/* + * The message structure, which is separate from the buffer, + * is initialized to reference the buffer + */ +void InitMessage(Message* message, uint8_t* buffer, size_t capacity) { + if (message == NULL) return; + memset(message, 0, sizeof(Message)); + message->base = buffer; + message->capacity = capacity; + message->size = 0; + message->read_offset = 0; + message->status = MESSAGE_STATUS_OK; +} + +/* + * The message structure is in the first sizeof(Memory) bytes + * of the buffer + */ +Message* CreateMessage(uint8_t* buffer, size_t buffer_size) { + if (buffer == NULL || buffer_size < sizeof(Message)) return NULL; + Message* message = (Message*)buffer; + message->base = buffer + sizeof(Message); + message->capacity = buffer_size - sizeof(Message); + message->size = 0; + message->read_offset = 0; + message->status = MESSAGE_STATUS_OK; + return message; +} + +/* + * Set the message to an empty state + */ +void ResetMessage(Message* message) { + message->size = 0; + message->read_offset = 0; + message->status = MESSAGE_STATUS_OK; +} + +uint8_t* GetBase(Message* message) { + if (message == NULL) return NULL; + return message->base; +} + +size_t GetCapacity(Message* message) { + if (message == NULL) return 0; + return message->capacity; +} + +size_t GetSize(Message* message) { + if (message == NULL) return 0; + return message->size; +} + +void SetSize(Message* message, size_t size) { + if (message == NULL) return; + message->size = size; +} + +MessageStatus GetStatus(Message* message) { return message->status; } + +void SetStatus(Message* message, MessageStatus status) { + message->status = status; +} + +size_t GetOffset(Message* message) { + if (message == NULL) return 0; + return message->read_offset; +} + +size_t SizeOfMessageStruct() { return sizeof(Message); } diff --git a/libwvdrmengine/oemcrypto/odk/test/odk_fuzz.cpp b/libwvdrmengine/oemcrypto/odk/test/odk_fuzz.cpp new file mode 100644 index 00000000..1ad80b71 --- /dev/null +++ b/libwvdrmengine/oemcrypto/odk/test/odk_fuzz.cpp @@ -0,0 +1,223 @@ +/* + * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary + * source code may only be used and distributed under the Widevine Master + * License Agreement. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "OEMCryptoCENCCommon.h" +#include "odk.h" +#include "odk_serialize.h" +#include "oec_util.h" + +using namespace std; +using namespace oec_util; + +typedef std::function roundtrip_fun; + +// @ kdo deserialize; odk derialize +static OEMCryptoResult odk_fun_LicenseRequest( + uint8_t* out, size_t* size, uint32_t api_version, uint32_t nonce, + uint32_t session_id, const ODK_LicenseRequest& /*core_license_request*/) { + return ODK_PrepareCoreLicenseRequest(out, SIZE_MAX, size, api_version, nonce, + session_id); +} + +static OEMCryptoResult odk_fun_RenewalRequest( + uint8_t* out, size_t* size, uint32_t api_version, uint32_t nonce, + uint32_t session_id, const ODK_RenewalRequest& core_renewal) { + // todo: fuzz ODK_ClockValues + ODK_ClockValues clock = {}; + uint64_t system_time_seconds = core_renewal.playback_time; + return ODK_PrepareCoreRenewalRequest(out, SIZE_MAX, size, api_version, nonce, + session_id, &clock, system_time_seconds); +} + +static OEMCryptoResult odk_fun_ProvisioningRequest( + uint8_t* out, size_t* size, uint32_t api_version, uint32_t nonce, + uint32_t session_id, const ODK_ProvisioningRequest& core_provisioning) { + const string& device_id = core_provisioning.device_id; + return ODK_PrepareCoreProvisioningRequest( + out, SIZE_MAX, size, api_version, nonce, session_id, + reinterpret_cast(device_id.data()), device_id.size()); +} + +template +static roundtrip_fun kdo_odk(const F& kdo_fun, const G& odk_fun) { + auto roundtrip = [&](const uint8_t* in, uint8_t* out, size_t size) -> size_t { + string input(reinterpret_cast(in), size); + T t = {}; + if (!kdo_fun(input, &t)) { + return 0; + } + OEMCryptoResult err = + odk_fun(out, &size, t.api_version, t.nonce, t.session_id, t); + return OEMCrypto_SUCCESS == err ? size : 0; + }; + return roundtrip; +} + +// @ odk deserialize; kdo serialize +namespace { +struct ODK_Common_Args { + uint32_t api_version; + uint32_t nonce; + uint32_t session_id; +}; +struct ODK_ParseLicense_Args { + ODK_Common_Args common; + uint8_t initial_license_load; + uint8_t usage_entry_present; +}; +struct ODK_ParseRenewal_Args { + ODK_Common_Args common; + uint64_t system_time; + ODK_TimerLimits timer_limits; + ODK_ClockValues clock_values; +}; +struct ODK_ParseProvisioning_Args { + ODK_Common_Args common; + size_t device_id_length; + uint8_t device_id[64]; +}; +} // namespace + +static OEMCryptoResult odk_fun_LicenseResponse( + const uint8_t* message, size_t message_length, uint32_t api_version, + uint32_t nonce, uint32_t session_id, const ODK_ParseLicense_Args* a, + ODK_ParsedLicense& parsed_lic) { + return ODK_ParseLicense(message, message_length, api_version, nonce, + session_id, bool(a->initial_license_load), + bool(a->usage_entry_present), &parsed_lic); +} + +static bool kdo_fun_LicenseResponse(const ODK_ParseLicense_Args* args, + const ODK_ParsedLicense& parsed_lic, + string* oemcrypto_core_message) { + const auto& common = args->common; + ODK_LicenseRequest core_request{common.api_version, common.nonce, + common.session_id}; + return CreateCoreLicenseResponse(parsed_lic, core_request, + oemcrypto_core_message); +} + +static OEMCryptoResult odk_fun_RenewalResponse( + const uint8_t* buf, size_t len, uint32_t api_version, uint32_t nonce, + uint32_t session_id, ODK_ParseRenewal_Args* a, + ODK_RenewalMessage& renewal_msg) { + uint64_t timer_value = 0; + OEMCryptoResult err = + ODK_ParseRenewal(buf, len, api_version, nonce, session_id, a->system_time, + &a->timer_limits, &a->clock_values, &timer_value); + if (OEMCrypto_SUCCESS == err) { + Message* msg = nullptr; + AllocateMessage(&msg, message_block); + InitMessage(msg, const_cast(buf), len); + SetSize(msg, len); + Unpack_ODK_RenewalMessage(msg, &renewal_msg); + assert(ValidMessage(msg)); + } + return err; +} + +static bool kdo_fun_RenewalResponse(const ODK_ParseRenewal_Args* args, + const ODK_RenewalMessage& renewal_msg, + string* oemcrypto_core_message) { + const auto& common = args->common; + ODK_RenewalRequest core_request{common.api_version, common.nonce, + common.session_id, renewal_msg.playback_time}; + return CreateCoreRenewalResponse(core_request, oemcrypto_core_message); +} + +static OEMCryptoResult odk_fun_ProvisioningResponse( + const uint8_t* buf, size_t len, uint32_t api_version, uint32_t nonce, + uint32_t session_id, ODK_ParseProvisioning_Args* a, + ODK_ParsedProvisioning& parsed_prov) { + return ODK_ParseProvisioning(buf, len, api_version, nonce, session_id, + a->device_id, a->device_id_length, &parsed_prov); +} + +static bool kdo_fun_ProvisioningResponse( + const ODK_ParseProvisioning_Args* args, + const ODK_ParsedProvisioning& parsed_prov, string* oemcrypto_core_message) { + const auto& common = args->common; + assert(args->device_id_length <= sizeof(args->device_id)); + ODK_ProvisioningRequest core_request{ + common.api_version, common.nonce, common.session_id, + string(reinterpret_cast(args->device_id), + args->device_id_length)}; + return CreateCoreProvisioningResponse(parsed_prov, core_request, + oemcrypto_core_message); +} + +template +static roundtrip_fun odk_kdo(const F& odk_fun, const G& kdo_fun) { + auto roundtrip = [&](const uint8_t* in, uint8_t* out, size_t size) -> size_t { + if (sizeof(A) > size) { + return 0; + } + + T t = {}; + const uint8_t* buf = in + sizeof(A); + size_t len = size - sizeof(A); + std::shared_ptr _args(new A()); + A* args = _args.get(); + memcpy(args, in, sizeof(A)); + const auto& common = args->common; + OEMCryptoResult err = odk_fun(buf, len, common.api_version, common.nonce, + common.session_id, args, t); + if (err != OEMCrypto_SUCCESS) { + return 0; + } + + string oemcrypto_core_message; + if (!kdo_fun(args, t, &oemcrypto_core_message)) { + return 0; + } + + assert(oemcrypto_core_message.size() <= size); + memcpy(out, oemcrypto_core_message.data(), oemcrypto_core_message.size()); + return oemcrypto_core_message.size(); + }; + return roundtrip; +} + +// @ fuzz raw -> parsed -> raw +static void verify_roundtrip(const uint8_t* in, size_t size, + roundtrip_fun roundtrip) { + std::vector _out(size); + auto out = _out.data(); + size_t n = roundtrip(in, out, size); + assert(!n || (n <= size && 0 == memcmp(in, out, n))); +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + verify_roundtrip( + data, size, + kdo_odk(ParseLicenseRequest, odk_fun_LicenseRequest)); + verify_roundtrip( + data, size, + kdo_odk(ParseRenewalRequest, odk_fun_RenewalRequest)); + verify_roundtrip(data, size, + kdo_odk( + ParseProvisioningRequest, odk_fun_ProvisioningRequest)); + verify_roundtrip(data, size, + odk_kdo( + odk_fun_LicenseResponse, kdo_fun_LicenseResponse)); + verify_roundtrip(data, size, + odk_kdo( + odk_fun_RenewalResponse, kdo_fun_RenewalResponse)); + verify_roundtrip( + data, size, + odk_kdo( + odk_fun_ProvisioningResponse, kdo_fun_ProvisioningResponse)); + + return 0; +} diff --git a/libwvdrmengine/oemcrypto/odk/test/odk_test.cpp b/libwvdrmengine/oemcrypto/odk/test/odk_test.cpp new file mode 100644 index 00000000..e21bc838 --- /dev/null +++ b/libwvdrmengine/oemcrypto/odk/test/odk_test.cpp @@ -0,0 +1,684 @@ +/* + * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary + * source code may only be used and distributed under the Widevine Master + * License Agreement. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "odk.h" +#include "odk_test.h" +#include "oec_util.h" + +using namespace oec_util; + +size_t ODK_FieldLength(ODK_FieldType type) { + switch (type) { + case ODK_UINT32: + return sizeof(uint32_t); + case ODK_UINT64: + return sizeof(uint64_t); + case ODK_SUBSTRING: + return sizeof(uint32_t) + sizeof(uint32_t); + case ODK_DEVICEID: + return ODK_DEVICE_ID_LEN_MAX; + case ODK_HASH: + return ODK_SHA256_HASH_SIZE; + default: + return SIZE_MAX; + } +} + +size_t ODK_AllocSize(ODK_FieldType type) { + if (type == ODK_SUBSTRING) { + return sizeof(OEMCrypto_Substring); + } + return ODK_FieldLength(type); +} + +OEMCryptoResult ODK_WriteSingleField(uint8_t* const buf, + const ODK_Field* const field) { + if (!buf || !field || !field->value) { + return ODK_ERROR_CORE_MESSAGE; + } + switch (field->type) { + case ODK_UINT32: { + uint32_t u32 = htobe32(*static_cast(field->value)); + memcpy(buf, &u32, sizeof(u32)); + break; + } + case ODK_UINT64: { + uint64_t u64 = htobe64(*static_cast(field->value)); + memcpy(buf, &u64, sizeof(u64)); + break; + } + case ODK_SUBSTRING: { + OEMCrypto_Substring* s = static_cast(field->value); + uint32_t off = htobe32(s->offset); + uint32_t len = htobe32(s->length); + memcpy(buf, &off, sizeof(off)); + memcpy(buf + sizeof(off), &len, sizeof(len)); + break; + } + case ODK_DEVICEID: + case ODK_HASH: { + const size_t field_len = ODK_FieldLength(field->type); + const uint8_t* const id = static_cast(field->value); + memcpy(buf, id, field_len); + + break; + } + default: + return ODK_ERROR_CORE_MESSAGE; + } + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult ODK_ReadSingleField(const uint8_t* const buf, + const ODK_Field* const field) { + if (!field || !field->value) { + return ODK_ERROR_CORE_MESSAGE; + } + switch (field->type) { + case ODK_UINT32: { + memcpy(field->value, buf, sizeof(uint32_t)); + uint32_t* u32p = static_cast(field->value); + *u32p = be32toh(*u32p); + break; + } + case ODK_UINT64: { + memcpy(field->value, buf, sizeof(uint64_t)); + uint64_t* u64p = static_cast(field->value); + *u64p = be64toh(*u64p); + break; + } + case ODK_SUBSTRING: { + OEMCrypto_Substring* s = static_cast(field->value); + uint32_t off = 0; + uint32_t len = 0; + memcpy(&off, buf, sizeof(off)); + memcpy(&len, buf + sizeof(off), sizeof(len)); + s->offset = be32toh(off); + s->length = be32toh(len); + break; + } + case ODK_DEVICEID: + case ODK_HASH: { + const size_t field_len = ODK_FieldLength(field->type); + uint8_t* const id = static_cast(field->value); + memcpy(id, buf, field_len); + break; + } + default: + return ODK_ERROR_CORE_MESSAGE; + } + return OEMCrypto_SUCCESS; +} + +/* + * Parameters: + * [in] size_in: buffer size + * [out] size_out: bytes processed + */ +OEMCryptoResult ODK_IterFields(ODK_FieldMode mode, uint8_t* const buf, + const size_t size_in, size_t* size_out, + std::vector& fields) { + if (!buf || !size_out) { + return ODK_ERROR_CORE_MESSAGE; + } + size_t off = 0, off2 = 0; + for (size_t i = 0; i < fields.size(); i++) { + if (__builtin_add_overflow(off, ODK_FieldLength(fields[i].type), &off2) || + off2 > size_in) { + return ODK_ERROR_CORE_MESSAGE; + } + uintptr_t base = reinterpret_cast(buf); + if (__builtin_add_overflow(base, off, &base)) { + return ODK_ERROR_CORE_MESSAGE; + } + uint8_t* const buf_off = buf + off; + if (mode == ODK_WRITE) { + ODK_WriteSingleField(buf_off, &fields[i]); + } else if (mode == ODK_READ) { + ODK_ReadSingleField(buf_off, &fields[i]); + } else { + return ODK_ERROR_CORE_MESSAGE; + } + off = off2; + } + *size_out = off; + if (*size_out > size_in) { + return ODK_ERROR_CORE_MESSAGE; + } + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult ODK_ReadFields(const uint8_t* const buf, const size_t size_in, + size_t* size_out, + std::vector& fields) { + return ODK_IterFields(ODK_READ, const_cast(buf), size_in, size_out, + fields); +} + +OEMCryptoResult ODK_WriteFields(uint8_t* const buf, const size_t size_in, + size_t* size_out, + std::vector& fields) { + return ODK_IterFields(ODK_WRITE, buf, size_in, size_out, fields); +} + +void expect_eq_buf(const void* s1, const void* s2, size_t n) { + if (memcmp(s1, s2, n)) { + const void* buffers[] = {s1, s2}; + for (int i = 0; i < 2; i++) { + char _tmp[] = "/tmp/fileXXXXXX"; + mkstemp(_tmp); + std::string tmp(_tmp); + std::fstream out(tmp, std::ios::out | std::ios::binary); + out.write((char*)buffers[i], n); + out.close(); + std: + std::cerr << "buffer " << i << " dumped to " << tmp << std::endl; + } + FAIL(); + } +} + +template +void ValidateRequest(uint32_t message_type, + std::vector& extra_fields, + const F& odk_prepare_func, const G& kdo_parse_func) { + uint32_t message_size = 0; + uint32_t api_version = 16; + uint32_t nonce = 0xdeadbeef; + uint32_t session_id = 0xcafebabe; + ODK_NonceValues nonce_values{api_version, nonce, session_id}; + std::vector total_fields = { + {ODK_UINT32, &message_type}, {ODK_UINT32, &message_size}, + {ODK_UINT32, &api_version}, {ODK_UINT32, &nonce}, + {ODK_UINT32, &session_id}, + }; + + total_fields.insert(total_fields.end(), extra_fields.begin(), + extra_fields.end()); + for (auto& field : total_fields) { + message_size += ODK_FieldLength(field.type); + } + + uint8_t* buf = new uint8_t[message_size](); + uint8_t* buf2 = new uint8_t[message_size](); + size_t bytes_written = message_size; + + EXPECT_EQ(OEMCrypto_SUCCESS, + odk_prepare_func(buf, &bytes_written, &nonce_values)); + EXPECT_EQ(bytes_written, message_size); + + EXPECT_EQ(OEMCrypto_SUCCESS, ODK_IterFields(ODK_WRITE, buf2, SIZE_MAX, + &bytes_written, total_fields)); + EXPECT_EQ(bytes_written, message_size); + + expect_eq_buf(buf, buf2, message_size); + + // odk kdo roundtrip + T t = {}; + std::string oemcrypto_core_message(reinterpret_cast(buf), + message_size); + EXPECT_TRUE(kdo_parse_func(oemcrypto_core_message, &t)); + nonce_values.api_version = t.api_version; + nonce_values.nonce = t.nonce; + nonce_values.session_id = t.session_id; + EXPECT_EQ(OEMCrypto_SUCCESS, + odk_prepare_func(buf2, &bytes_written, &nonce_values)); + EXPECT_EQ(bytes_written, message_size); + expect_eq_buf(buf, buf2, message_size); + + delete[] buf; + delete[] buf2; +} + +/** + * Template arguments: + * T: kdo input struct + * F: odk deserializer + * G: kdo serializer + */ +template +void ValidateResponse(uint32_t message_type, + std::vector& extra_fields, + const F& odk_parse_func, const G& kdo_prepare_func) { + uint32_t message_size = 0; + uint32_t api_version = 16; + uint32_t nonce = 0xdeadbeef; + uint32_t session_id = 0xcafebabe; + std::vector total_fields = { + {ODK_UINT32, &message_type}, {ODK_UINT32, &message_size}, + {ODK_UINT32, &api_version}, {ODK_UINT32, &nonce}, + {ODK_UINT32, &session_id}, + }; + + uint32_t header_size = 0; + for (auto& field : total_fields) { + header_size += ODK_FieldLength(field.type); + } + + total_fields.insert(total_fields.end(), extra_fields.begin(), + extra_fields.end()); + for (auto& field : total_fields) { + message_size += ODK_FieldLength(field.type); + } + + uint8_t* buf = new uint8_t[message_size](); + uint8_t* zero = new uint8_t[message_size](); + size_t bytes_read = 0, bytes_written = 0; + + T t = {}; + t.api_version = api_version; + t.nonce = nonce; + t.session_id = session_id; + + // serialize input to buf + EXPECT_EQ(OEMCrypto_SUCCESS, ODK_IterFields(ODK_WRITE, buf, SIZE_MAX, + &bytes_written, total_fields)); + EXPECT_EQ(bytes_written, message_size); + + // zero-out input + EXPECT_EQ(OEMCrypto_SUCCESS, ODK_IterFields(ODK_READ, zero, bytes_written, + &bytes_read, extra_fields)); + EXPECT_TRUE(bytes_written > bytes_read && + bytes_written - bytes_read == header_size); + + // parse buf with odk + ODK_NonceValues nonce_values{api_version, nonce, session_id}; + EXPECT_EQ(OEMCrypto_SUCCESS, + odk_parse_func(buf, bytes_written, &nonce_values)); + + // serialize odk output to oemcrypto_core_message + std::string oemcrypto_core_message; + EXPECT_TRUE(kdo_prepare_func(t, &oemcrypto_core_message)); + EXPECT_EQ(bytes_written, message_size); + + expect_eq_buf(buf, oemcrypto_core_message.data(), message_size); + delete[] buf; + delete[] zero; +} + +TEST(OdkTest, SerializeFields) { + uint32_t x[] = {0, 1, 2}; + uint64_t y[] = {3ll << 32, 4ll << 32, 5ll << 32}; + OEMCrypto_Substring s = {.offset = 6, .length = 7}; + std::vector fields = { + {ODK_UINT32, &x[0]}, {ODK_UINT32, &x[1]}, {ODK_UINT32, &x[2]}, + {ODK_UINT64, &y[0]}, {ODK_UINT64, &y[1]}, {ODK_UINT64, &y[2]}, + {ODK_SUBSTRING, &s}, + }; + uint8_t buf[1024] = {0}; + uint8_t buf2[1024] = {0}; + size_t bytes_read = 0, bytes_written = 0; + ODK_IterFields(ODK_WRITE, buf, SIZE_MAX, &bytes_read, fields); + ODK_IterFields(ODK_READ, buf, bytes_read, &bytes_written, fields); + ODK_IterFields(ODK_WRITE, buf2, SIZE_MAX, &bytes_read, fields); + + expect_eq_buf(buf, buf2, bytes_read); +} + +TEST(OdkTest, SerializeFieldsStress) { + const int n = 1024; + std::vector fields(n); + std::srand(0); + size_t total_size = 0; + for (int i = 0; i < n; i++) { + fields[i].type = static_cast(std::rand() % + static_cast(ODK_NUMTYPES)); + size_t field_size = ODK_AllocSize(fields[i].type); + fields[i].value = malloc(ODK_AllocSize(fields[i].type)); + total_size += ODK_FieldLength(fields[i].type); + } + + uint8_t* buf = new uint8_t[total_size]; + for (int i = 0; i < total_size; i++) { + buf[i] = std::rand() & 0xff; + } + + size_t bytes_read = 0, bytes_written = 0; + uint8_t* buf2 = new uint8_t[total_size]; + ODK_IterFields(ODK_READ, buf, total_size, &bytes_read, fields); + EXPECT_EQ(bytes_read, total_size); + ODK_IterFields(ODK_WRITE, buf2, total_size, &bytes_written, fields); + EXPECT_EQ(bytes_written, total_size); + + expect_eq_buf(buf, buf2, total_size); + + // cleanup + for (int i = 0; i < n; i++) { + free(fields[i].value); + } + delete[] buf; + delete[] buf2; +} + +TEST(OdkTest, LicenseRequest) { + std::vector empty; + auto odk_prepare_func = [&](uint8_t* const buf, size_t* size, + ODK_NonceValues* nonce_values) { + return ODK_PrepareCoreLicenseRequest(buf, SIZE_MAX, size, nonce_values); + }; + auto kdo_parse_func = ParseLicenseRequest; + ValidateRequest(ODK_License_Request_Type, empty, + odk_prepare_func, kdo_parse_func); +} + +TEST(OdkTest, RenewalRequest) { + uint64_t system_time_seconds = 0xBADDCAFE000FF1CE; + std::vector extra_fields = { + {ODK_UINT64, &system_time_seconds}, + }; + ODK_ClockValues clock_values = {0}; + auto odk_prepare_func = [&](uint8_t* const buf, size_t* size, + const ODK_NonceValues* nonce_values) { + return ODK_PrepareCoreRenewalRequest(buf, SIZE_MAX, size, nonce_values, + &clock_values, system_time_seconds); + }; + auto kdo_parse_func = [&](const std::string& oemcrypto_core_message, + ODK_RenewalRequest* core_renewal_request) { + bool ok = ParseRenewalRequest(oemcrypto_core_message, core_renewal_request); + if (ok) { + system_time_seconds = core_renewal_request->playback_time; + } + return ok; + }; + ValidateRequest(ODK_Renewal_Request_Type, extra_fields, + odk_prepare_func, kdo_parse_func); +} + +TEST(OdkTest, ProvisionRequest) { + uint32_t device_id_length = DEVICE_ID_MAX / 2; + uint8_t device_id[DEVICE_ID_MAX] = {0}; + memset(device_id, 0xff, device_id_length); + std::vector extra_fields = { + {ODK_UINT32, &device_id_length}, + {ODK_DEVICEID, device_id}, + }; + auto odk_prepare_func = [&](uint8_t* const buf, size_t* size, + const ODK_NonceValues* nonce_values) { + return ODK_PrepareCoreProvisioningRequest(buf, SIZE_MAX, size, nonce_values, + device_id, device_id_length); + }; + auto kdo_parse_func = + [&](const std::string& oemcrypto_core_message, + ODK_ProvisioningRequest* core_provisioning_request) { + bool ok = ParseProvisioningRequest(oemcrypto_core_message, + core_provisioning_request); + if (ok) { + const std::string& device_id_str = + core_provisioning_request->device_id; + device_id_length = device_id_str.size(); + memcpy(device_id, device_id_str.data(), device_id_length); + } + return ok; + }; + ValidateRequest(ODK_Provisioning_Request_Type, + extra_fields, odk_prepare_func, + kdo_parse_func); +} + +TEST(OdkTest, LicenseResponse) { + ODK_ParsedLicense parsed_license = { + .enc_mac_keys_iv = {.offset = 0, .length = 1}, + .enc_mac_keys = {.offset = 2, .length = 3}, + .pst = {.offset = 4, .length = 5}, + .srm_restriction_data = {.offset = 6, .length = 7}, + .license_type = 8, + .nonce_required = 0xDEADC0DE, + .timer_limits = + { + .soft_expiry = 9, + .earliest_playback_start_seconds = 10, + .latest_playback_start_seconds = 11, + .initial_playback_duration_seconds = 12, + .renewal_playback_duration_seconds = 13, + .license_duration_seconds = 14, + }, + .request_hash = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}, + .key_array_length = 3, + .key_array = + { + { + .key_id = {.offset = 15, .length = 16}, + .key_data_iv = {.offset = 17, .length = 18}, + .key_data = {.offset = 19, .length = 20}, + .key_control_iv = {.offset = 21, .length = 22}, + .key_control = {.offset = 23, .length = 24}, + }, + { + .key_id = {.offset = 25, .length = 26}, + .key_data_iv = {.offset = 27, .length = 28}, + .key_data = {.offset = 29, .length = 30}, + .key_control_iv = {.offset = 31, .length = 32}, + .key_control = {.offset = 33, .length = 34}, + }, + { + .key_id = {.offset = 35, .length = 36}, + .key_data_iv = {.offset = 37, .length = 38}, + .key_data = {.offset = 39, .length = 40}, + .key_control_iv = {.offset = 41, .length = 42}, + .key_control = {.offset = 43, .length = 44}, + }, + }, + }; + + uint32_t message_type = ODK_License_Response_Type; + std::vector extra_fields = { + {ODK_SUBSTRING, &parsed_license.enc_mac_keys_iv}, + {ODK_SUBSTRING, &parsed_license.enc_mac_keys}, + {ODK_SUBSTRING, &parsed_license.pst}, + {ODK_SUBSTRING, &parsed_license.srm_restriction_data}, + {ODK_UINT32, &parsed_license.license_type}, + {ODK_UINT32, &parsed_license.nonce_required}, + {ODK_UINT32, &parsed_license.timer_limits.soft_expiry}, + {ODK_UINT64, + &parsed_license.timer_limits.earliest_playback_start_seconds}, + {ODK_UINT64, &parsed_license.timer_limits.latest_playback_start_seconds}, + {ODK_UINT64, + &parsed_license.timer_limits.initial_playback_duration_seconds}, + {ODK_UINT64, + &parsed_license.timer_limits.renewal_playback_duration_seconds}, + {ODK_UINT64, &parsed_license.timer_limits.license_duration_seconds}, + {ODK_HASH, &parsed_license.request_hash}, + {ODK_UINT32, &parsed_license.key_array_length}, + {ODK_SUBSTRING, &parsed_license.key_array[0].key_id}, + {ODK_SUBSTRING, &parsed_license.key_array[0].key_data_iv}, + {ODK_SUBSTRING, &parsed_license.key_array[0].key_data}, + {ODK_SUBSTRING, &parsed_license.key_array[0].key_control_iv}, + {ODK_SUBSTRING, &parsed_license.key_array[0].key_control}, + {ODK_SUBSTRING, &parsed_license.key_array[1].key_id}, + {ODK_SUBSTRING, &parsed_license.key_array[1].key_data_iv}, + {ODK_SUBSTRING, &parsed_license.key_array[1].key_data}, + {ODK_SUBSTRING, &parsed_license.key_array[1].key_control_iv}, + {ODK_SUBSTRING, &parsed_license.key_array[1].key_control}, + {ODK_SUBSTRING, &parsed_license.key_array[2].key_id}, + {ODK_SUBSTRING, &parsed_license.key_array[2].key_data_iv}, + {ODK_SUBSTRING, &parsed_license.key_array[2].key_data}, + {ODK_SUBSTRING, &parsed_license.key_array[2].key_control_iv}, + {ODK_SUBSTRING, &parsed_license.key_array[2].key_control}, + }; + + uint8_t request_hash[ODK_SHA256_HASH_SIZE] = {}; + memcpy(request_hash, parsed_license.request_hash, ODK_SHA256_HASH_SIZE); + auto odk_parse_func = [&](const uint8_t* buf, size_t size, + ODK_NonceValues* nonce_values) { + return ODK_ParseLicense(buf, size + 128, size, 1, 0, request_hash, nullptr, + nullptr, nonce_values, &parsed_license); + }; + auto kdo_prepare_func = [&](const ODK_LicenseRequest& core_request, + std::string* oemcrypto_core_message) { + return CreateCoreLicenseResponse(parsed_license, core_request, + oemcrypto_core_message); + }; + ValidateResponse(ODK_License_Response_Type, extra_fields, + odk_parse_func, kdo_prepare_func); +} + +TEST(OdkTest, RenewalResponse) { + uint64_t system_time = 0xfaceb00c; + uint64_t playback_clock = 11; + uint64_t playback_timer = 12; + uint64_t message_playback_clock = 10; + std::vector extra_fields = { + {ODK_UINT64, &message_playback_clock}, + }; + + ODK_TimerLimits timer_limits = { + .soft_expiry = 0, + .earliest_playback_start_seconds = 0, + .latest_playback_start_seconds = 100, + .initial_playback_duration_seconds = 10, + .renewal_playback_duration_seconds = 20, + .license_duration_seconds = 100, + }; + + ODK_ClockValues clock_values = { + .time_of_license_signed = 0, + .time_of_first_decrypt = system_time - playback_clock, + .time_of_last_decrypt = 0, + .time_when_timer_expires = system_time + playback_timer, + .timer_status = 0, + .status = kUnused, + }; + + auto odk_parse_func = [&](const uint8_t* buf, size_t size, + ODK_NonceValues* nonce_values) { + OEMCryptoResult err = + ODK_ParseRenewal(buf, size, size, nonce_values, system_time, + &timer_limits, &clock_values, &playback_timer); + + EXPECT_EQ(ODK_SET_TIMER, err); + EXPECT_EQ(timer_limits.renewal_playback_duration_seconds, playback_timer); + EXPECT_EQ(clock_values.time_when_timer_expires, + system_time + playback_timer); + + // manually restore message_playback_clock since ODK_ParseRenewal doesn't + // generate output + message_playback_clock = 10; + return OEMCrypto_SUCCESS; + }; + auto kdo_prepare_func = [&](ODK_RenewalRequest& core_request, + std::string* oemcrypto_core_message) { + core_request.playback_time = message_playback_clock; + return CreateCoreRenewalResponse(core_request, oemcrypto_core_message); + }; + ValidateResponse(ODK_Renewal_Response_Type, extra_fields, + odk_parse_func, kdo_prepare_func); +} + +TEST(OdkTest, ProvisionResponse) { + uint32_t device_id_length = DEVICE_ID_MAX / 2; + uint8_t device_id[DEVICE_ID_MAX] = {0}; + memset(device_id, 0xff, device_id_length); + + ODK_ParsedProvisioning parsed_response = { + .enc_private_key = {.offset = 0, .length = 1}, + .enc_private_key_iv = {.offset = 2, .length = 3}, + .encrypted_message_key = {.offset = 4, .length = 5}, + }; + + std::vector extra_fields = { + {ODK_UINT32, &device_id_length}, + {ODK_DEVICEID, device_id}, + {ODK_UINT32, &parsed_response.key_type}, + {ODK_SUBSTRING, &parsed_response.enc_private_key}, + {ODK_SUBSTRING, &parsed_response.enc_private_key_iv}, + {ODK_SUBSTRING, &parsed_response.encrypted_message_key}, + }; + + auto odk_parse_func = [&](const uint8_t* buf, size_t size, + ODK_NonceValues* nonce_values) { + // restore device id because it is not part of parsed_response + device_id_length = DEVICE_ID_MAX / 2; + memset(device_id, 0xff, device_id_length); + OEMCryptoResult err = + ODK_ParseProvisioning(buf, size + 16, size, nonce_values, device_id, + device_id_length, &parsed_response); + return err; + }; + auto kdo_prepare_func = [&](ODK_ProvisioningRequest& core_request, + std::string* oemcrypto_core_message) { + core_request.device_id.assign(reinterpret_cast(device_id), + device_id_length); + return CreateCoreProvisioningResponse(parsed_response, core_request, + oemcrypto_core_message); + }; + ValidateResponse(ODK_Provisioning_Response_Type, + extra_fields, odk_parse_func, + kdo_prepare_func); +} + +TEST(OdkSizeTest, LicenseRequest) { + uint8_t* message = nullptr; + size_t message_length = 0; + size_t core_message_length = 0; + uint32_t api_version = 0; + uint32_t nonce = 0; + uint32_t session_id = 0; + ODK_NonceValues nonce_values{api_version, nonce, session_id}; + EXPECT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, + ODK_PrepareCoreLicenseRequest(message, message_length, + &core_message_length, &nonce_values)); + // All messages have at least a five 4-byte fields. + size_t minimum_message_size = 5 * 4; + EXPECT_GE(core_message_length, minimum_message_size); +} + +TEST(OdkSizeTest, RenewalRequest) { + uint8_t* message = nullptr; + size_t message_length = 0; + size_t core_message_length = 0; + uint32_t api_version = 0; + uint32_t nonce = 0; + uint32_t session_id = 0; + ODK_ClockValues clock_values = {}; + clock_values.time_of_first_decrypt = 10; + uint64_t system_time_seconds = 15; + ODK_NonceValues nonce_values{api_version, nonce, session_id}; + EXPECT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, + ODK_PrepareCoreRenewalRequest(message, message_length, + &core_message_length, &nonce_values, + &clock_values, system_time_seconds)); + // All messages have at least a five 4-byte fields. + size_t minimum_message_size = 5 * 4; + EXPECT_GE(core_message_length, minimum_message_size); +} + +TEST(OdkSizeTest, ProvisioningRequest) { + uint8_t* message = nullptr; + size_t message_length = 0; + size_t core_message_length = 0; + uint32_t api_version = 0; + uint32_t nonce = 0; + uint32_t session_id = 0; + uint8_t* device_id = nullptr; + uint32_t device_id_length = 0; + ODK_NonceValues nonce_values{api_version, nonce, session_id}; + EXPECT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, + ODK_PrepareCoreProvisioningRequest( + message, message_length, &core_message_length, &nonce_values, + nullptr, device_id_length)); + // All messages have at least a five 4-byte fields. + size_t minimum_message_size = 5 * 4; + EXPECT_GE(core_message_length, minimum_message_size); +} diff --git a/libwvdrmengine/oemcrypto/odk/test/odk_test.h b/libwvdrmengine/oemcrypto/odk/test/odk_test.h new file mode 100644 index 00000000..a8114a10 --- /dev/null +++ b/libwvdrmengine/oemcrypto/odk/test/odk_test.h @@ -0,0 +1,64 @@ +/* + * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary + * source code may only be used and distributed under the Widevine Master + * License Agreement. + */ + +#ifndef ODK_TEST_H_ +#define ODK_TEST_H_ + +#include "OEMCryptoCENCCommon.h" + +typedef enum { + ODK_License_Request_Type = 1, + ODK_License_Response_Type = 2, + ODK_Renewal_Request_Type = 3, + ODK_Renewal_Response_Type = 4, + ODK_Provisioning_Request_Type = 5, + ODK_Provisioning_Response_Type = 6, +} ODK_MessageType; + +typedef enum { + ODK_UINT32, + ODK_UINT64, + ODK_SUBSTRING, + ODK_DEVICEID, + ODK_HASH, + ODK_NUMTYPES, +} ODK_FieldType; + +typedef enum { + ODK_READ, + ODK_WRITE, +} ODK_FieldMode; + +typedef struct { + ODK_FieldType type; + void* value; +} ODK_Field; + +#define DEVICE_ID_MAX (64) + +#ifdef __cplusplus +extern "C" { +#endif + +size_t ODK_FieldLength(ODK_FieldType type); +OEMCryptoResult ODK_WriteSingleField(uint8_t* const buf, + const ODK_Field* const field); +OEMCryptoResult ODK_ReadSingleField(const uint8_t* const buf, + const ODK_Field* const field); + +OEMCryptoResult ODK_ReadFields(const uint8_t* const buf, const size_t size_in, + size_t* size_out, const size_t n, + const ODK_Field* const fields); + +OEMCryptoResult ODK_WriteFields(uint8_t* const buf, const size_t size_in, + size_t* size_out, const size_t n, + const ODK_Field* const fields); + +#ifdef __cplusplus +} +#endif + +#endif // ODK_TEST_H_ diff --git a/libwvdrmengine/oemcrypto/odk/test/odk_timer_test.cpp b/libwvdrmengine/oemcrypto/odk/test/odk_timer_test.cpp new file mode 100644 index 00000000..de398103 --- /dev/null +++ b/libwvdrmengine/oemcrypto/odk/test/odk_timer_test.cpp @@ -0,0 +1,936 @@ +/* + * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary + * source code may only be used and distributed under the Widevine Master + * License Agreement. + */ + +#include + +#include "odk.h" + +using ::testing::tuple; +using ::testing::Values; +using ::testing::WithParamInterface; + +namespace { +constexpr uint64_t kTolerance = 1; // Allow 1 second of roundoff. +} // namespace + +namespace odk_test { +struct ServerExpiry { + bool soft_rental; + bool soft_playback; +}; + +TEST(OdkTimerBasicTest, NullTest) { + // Assert that nullptr does not cause a core dump. + ODK_InitializeClockValues(nullptr, 0u); + ODK_ReloadClockValues(nullptr, 0u, 0u, 0u, kActive, 0u); + ODK_AttemptFirstPlayback(0u, nullptr, nullptr, nullptr); + ODK_UpdateLastPlaybackTime(0, nullptr, nullptr); + ASSERT_TRUE(true); +} + +TEST(OdkTimerBasicTest, Init) { + // Verify that basic initialization sets all of the fields. + ODK_ClockValues clock_values; + uint64_t time = 42; + ODK_InitializeClockValues(&clock_values, time); + EXPECT_EQ(clock_values.time_of_license_signed, time); + EXPECT_EQ(clock_values.time_of_first_decrypt, 0); + EXPECT_EQ(clock_values.time_of_last_decrypt, 0); + EXPECT_EQ(clock_values.time_when_timer_expires, 0); + EXPECT_EQ(clock_values.timer_status, 0); + EXPECT_EQ(clock_values.status, kUnused); +} + +TEST(OdkTimerBasicTest, Reload) { + // Verify that reloading clock values uses the same values + // for fields that can be saved, and sets others to 0. + ODK_ClockValues clock_values; + uint64_t time = 42u; + uint64_t lic_signed = 1u; + uint64_t first_decrypt = 2u; + uint64_t last_decrypt = 3u; + enum OEMCrypto_Usage_Entry_Status status = kInactiveUsed; + ODK_ReloadClockValues(&clock_values, lic_signed, first_decrypt, last_decrypt, + status, time); + EXPECT_EQ(clock_values.time_of_license_signed, lic_signed); + EXPECT_EQ(clock_values.time_of_first_decrypt, first_decrypt); + EXPECT_EQ(clock_values.time_of_last_decrypt, last_decrypt); + EXPECT_EQ(clock_values.time_when_timer_expires, 0u); + EXPECT_EQ(clock_values.timer_status, 0u); + EXPECT_EQ(clock_values.status, status); +} + +// ************************************************************************ +// Test that we can only start playback within the rental window. This +// simulates requesting a license at rental_clock_start_, and a rental window of +// 50-150s relative to license request, or 100-200s absolute. +class OdkTimerRentalWindow : public ::testing::Test { + public: + OdkTimerRentalWindow() { + rental_clock_start_ = 10000u; + rental_window_start_ = 50u; + rental_window_duration_ = 100u; + } + + protected: + void SetUp() override { + ::testing::Test::SetUp(); + // Start rental clocks at rental_clock_start_. + ODK_InitializeClockValues(&clock_values_, rental_clock_start_); + // Simple license values: + timer_limits_.soft_expiry = false; + timer_limits_.earliest_playback_start_seconds = rental_window_start_; + timer_limits_.latest_playback_start_seconds = + rental_window_start_ + rental_window_duration_; + timer_limits_.initial_playback_duration_seconds = 0; + timer_limits_.renewal_playback_duration_seconds = 0; + timer_limits_.license_duration_seconds = 0; + } + + // Simulate reloading a license in a new session. An offline license should + // have several of the clock_value's fields saved into the usage table. When + // it is reloaded those values should be reloaded. From these fields, the + // ODK function can tell if this is the first playback for the license, or + // just the first playback for this session. The key fields that should be + // saved are the status, and the times for license signed, and first and + // last playback times. + void ReloadClock(uint64_t system_time) { + ODK_ClockValues old_clock_values = clock_values_; + // First clear out the old clock values. + ODK_InitializeClockValues(&clock_values_, 0u); + ODK_ReloadClockValues(&clock_values_, + old_clock_values.time_of_license_signed, + old_clock_values.time_of_first_decrypt, + old_clock_values.time_of_last_decrypt, + old_clock_values.status, system_time); + } + + uint64_t system_time(uint64_t rental_clock) { + return rental_clock_start_ + rental_clock; + } + + ODK_TimerLimits timer_limits_; + ODK_ClockValues clock_values_; + + // The rental clock starts when the request is signed. + uint64_t rental_clock_start_; + // start of rental window in seconds since rental clock start. + uint64_t rental_window_start_; + // The "width" of window. + uint64_t rental_window_duration_; +}; + +TEST_F(OdkTimerRentalWindow, EarlyTest) { + uint64_t timer_value = 0; + // An attempt to start playback before the timer starts is an error. + // We use the TIMER_EXPIRED error to mean both early or late. + const uint64_t bad_start_time = + system_time(timer_limits_.earliest_playback_start_seconds - 10); + EXPECT_EQ(ODK_TIMER_EXPIRED, + ODK_AttemptFirstPlayback(bad_start_time, &timer_limits_, + &clock_values_, &timer_value)); + const uint64_t good_start_time = + system_time(timer_limits_.earliest_playback_start_seconds); + EXPECT_EQ(ODK_DISABLE_TIMER, + ODK_AttemptFirstPlayback(good_start_time, &timer_limits_, + &clock_values_, &timer_value)); + EXPECT_EQ(kActive, clock_values_.status); + EXPECT_EQ(good_start_time, clock_values_.time_of_first_decrypt); + EXPECT_EQ(good_start_time, clock_values_.time_of_last_decrypt); + EXPECT_EQ(0, clock_values_.time_when_timer_expires); +} + +TEST_F(OdkTimerRentalWindow, NullTimer) { + // If OEMCrypto passes in a nullpointer, then the ODK should allow this. + uint64_t* timer_value_pointer = nullptr; + const uint64_t bad_start_time = + system_time(timer_limits_.earliest_playback_start_seconds - 10); + EXPECT_EQ(ODK_TIMER_EXPIRED, + ODK_AttemptFirstPlayback(bad_start_time, &timer_limits_, + &clock_values_, timer_value_pointer)); + const uint64_t good_start_time = + system_time(timer_limits_.earliest_playback_start_seconds); + EXPECT_EQ(ODK_DISABLE_TIMER, + ODK_AttemptFirstPlayback(good_start_time, &timer_limits_, + &clock_values_, timer_value_pointer)); + EXPECT_EQ(kActive, clock_values_.status); + EXPECT_EQ(good_start_time, clock_values_.time_of_first_decrypt); + EXPECT_EQ(good_start_time, clock_values_.time_of_last_decrypt); + EXPECT_EQ(0, clock_values_.time_when_timer_expires); +} + +// Verify that an attempt to start playback outside the rental window fails. +TEST_F(OdkTimerRentalWindow, LateTest) { + uint64_t timer_value = 0; + // An attempt to start playback before the timer starts is an error. + // We use the TIMER_EXPIRED error to mean both early or late. + const uint64_t start_time = system_time(201); + EXPECT_EQ(ODK_TIMER_EXPIRED, + ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, + &timer_value)); + EXPECT_EQ(kUnused, clock_values_.status); +} + +// ************************************************************************ +// The Hard Rental use case is a strict time limit on playback. +class OdkTimerHardTest : public OdkTimerRentalWindow { + protected: + void SetUp() override { + OdkTimerRentalWindow::SetUp(); + timer_limits_.soft_expiry = false; // Hard expiry. + // License duration is 200. The license starts when it was signed at 50, + // so the license is valid from 50-250. The rental window is 100-200 -- as + // inherited from the ODKRentalWindow class. + timer_limits_.license_duration_seconds = 200u; + } +}; + +TEST_F(OdkTimerHardTest, EarlyTest) { + uint64_t timer_value = 0; + // An attempt to start playback before the timer starts is an error. + // We use the TIMER_EXPIRED error to mean both early or late. + const uint64_t bad_start_time = + system_time(timer_limits_.earliest_playback_start_seconds - 10); + EXPECT_EQ(ODK_TIMER_EXPIRED, + ODK_AttemptFirstPlayback(bad_start_time, &timer_limits_, + &clock_values_, &timer_value)); + EXPECT_EQ(kUnused, clock_values_.status); + // Starting playback within the window should work. + const uint64_t start_time = + system_time(timer_limits_.earliest_playback_start_seconds); + const uint64_t cutoff_time = + system_time(timer_limits_.license_duration_seconds); + // For a soft_expiry = false, we should set a timer. + EXPECT_EQ(ODK_SET_TIMER, + ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, + &timer_value)); + EXPECT_EQ(kActive, clock_values_.status); + EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); + EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); + EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); + EXPECT_NEAR(cutoff_time - start_time, timer_value, kTolerance); +} + +TEST_F(OdkTimerHardTest, NormalTest) { + uint64_t timer_value = 0; + // Starting playback within the window should work. + const uint64_t start_time = + system_time(timer_limits_.earliest_playback_start_seconds); + const uint64_t cutoff_time = + system_time(timer_limits_.license_duration_seconds); + // For a soft_expiry = false, we should set a timer. + EXPECT_EQ(ODK_SET_TIMER, + ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, + &timer_value)); + EXPECT_EQ(kActive, clock_values_.status); + EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); + EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); + EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); + EXPECT_NEAR(cutoff_time - start_time, timer_value, kTolerance); + // Try to play before the cutoff time is valid. + const uint64_t valid_play_time = cutoff_time - 1; + // This play time is outside the rental window. We are allowed to continue + // playback after the rental window expires as long as the first decrypt is + // within the rental window. + EXPECT_LE(timer_limits_.latest_playback_start_seconds, valid_play_time); + EXPECT_EQ(OEMCrypto_SUCCESS, + ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_, + &clock_values_)); + EXPECT_EQ(kActive, clock_values_.status); + // First decrypt should NOT change. + EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); + // Last decrypt should change. + EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); + EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); + // Try to play after the cutoff time is not valid. + const uint64_t late_play_time = cutoff_time + 1; + EXPECT_EQ(ODK_TIMER_EXPIRED, + ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_, + &clock_values_)); + // First decrypt should NOT change. + EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); + // Last decrypt should NOT change because we were not allowed to decrypt. + EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); + EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); +} + +// This verifies that the rental window only affects the first load for the +// license, not the first load for the session. +TEST_F(OdkTimerHardTest, Reload) { + uint64_t timer_value = 0; + // An attempt to start playback before the timer starts is an error. + // We use the TIMER_EXPIRED error to mean both early or late. + EXPECT_EQ(ODK_TIMER_EXPIRED, + ODK_AttemptFirstPlayback(75, &timer_limits_, &clock_values_, + &timer_value)); + EXPECT_EQ(kUnused, clock_values_.status); + // Starting playback within the window should work. + const uint64_t start_time = + system_time(timer_limits_.earliest_playback_start_seconds); + const uint64_t cutoff_time = + system_time(timer_limits_.license_duration_seconds); + // For a soft_expiry = false, we should set a timer. + EXPECT_EQ(ODK_SET_TIMER, + ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, + &timer_value)); + EXPECT_EQ(kActive, clock_values_.status); + EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); + EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); + EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); + EXPECT_NEAR(cutoff_time - start_time, timer_value, kTolerance); + + // We can restart playback before the cutoff time. + const uint64_t valid_restart_time = cutoff_time - 10; + ReloadClock(valid_restart_time); + EXPECT_EQ(kActive, clock_values_.status); + + // This restart is outside the rental window. We are allowed to + // restart playback after the rental window expires as long as the first + // decrypt for the license is within the rental window. + EXPECT_LE(timer_limits_.latest_playback_start_seconds, valid_restart_time); + EXPECT_EQ(ODK_SET_TIMER, + ODK_AttemptFirstPlayback(valid_restart_time, &timer_limits_, + &clock_values_, &timer_value)); + EXPECT_EQ(kActive, clock_values_.status); + EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); + EXPECT_EQ(valid_restart_time, clock_values_.time_of_last_decrypt); + EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); + EXPECT_NEAR(cutoff_time - valid_restart_time, timer_value, kTolerance); + + // Try to play before the cutoff time is valid. + const uint64_t valid_play_time = cutoff_time - 1; + // This play time is outside the rental window. That means we are allowed + // to continue playback after the rental window expires as long as the first + // decrypt is within the rental window. + EXPECT_LE(timer_limits_.latest_playback_start_seconds, valid_play_time); + EXPECT_EQ(OEMCrypto_SUCCESS, + ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_, + &clock_values_)); + EXPECT_EQ(kActive, clock_values_.status); + // First decrypt should NOT change. + EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); + // Last decrypt should change. + EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); + EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); + // Try to play after the cutoff time is not valid. + const uint64_t late_play_time = cutoff_time + 1; + EXPECT_EQ(ODK_TIMER_EXPIRED, + ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_, + &clock_values_)); + // First decrypt should NOT change. + EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); + // Last decrypt should NOT change because we were not allowed to decrypt. + EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); + EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); +} + +TEST_F(OdkTimerHardTest, ReloadLate) { + uint64_t timer_value = 0; + // Starting playback within the window should work. + const uint64_t start_time = + system_time(timer_limits_.earliest_playback_start_seconds); + const uint64_t cutoff_time = + system_time(timer_limits_.license_duration_seconds); + // For a soft_expiry = false, we should set a timer. + EXPECT_EQ(ODK_SET_TIMER, + ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, + &timer_value)); + EXPECT_EQ(kActive, clock_values_.status); + EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); + EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); + EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); + EXPECT_NEAR(cutoff_time - start_time, timer_value, kTolerance); + + // We can not restart playback after the cutoff time. + const uint64_t late_restart_time = cutoff_time + 10; + ReloadClock(late_restart_time); + EXPECT_EQ(kActive, clock_values_.status); + EXPECT_EQ(ODK_TIMER_EXPIRED, + ODK_AttemptFirstPlayback(late_restart_time, &timer_limits_, + &clock_values_, &timer_value)); + EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); + EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); + + // Calling UpdateLastPlaybackTimer should not be done, but if it were done, + // it should also fail. + EXPECT_EQ(ODK_TIMER_EXPIRED, + ODK_UpdateLastPlaybackTime(late_restart_time, &timer_limits_, + &clock_values_)); + // First decrypt should NOT change. + EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); + // Last decrypt should NOT change because we were not allowed to decrypt. + EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); +} + +// ************************************************************************ +// The Soft Rental use case is a strict time limit on playback. +class OdkTimerSoftTest : public OdkTimerRentalWindow { + protected: + void SetUp() override { + OdkTimerRentalWindow::SetUp(); + timer_limits_.soft_expiry = true; // Soft expiry. + // License duration is 200. The license starts when it was signed at 50, + // so the license is valid from 50-250. The rental window is 100-200 -- as + // inherited from the ODKRentalWindow class. + timer_limits_.license_duration_seconds = 200u; + } +}; + +TEST_F(OdkTimerSoftTest, EarlyTest) { + uint64_t timer_value = 0; + // An attempt to start playback before the timer starts is an error. + // We use the TIMER_EXPIRED error to mean both early or late. + const uint64_t bad_start_time = + system_time(timer_limits_.earliest_playback_start_seconds - 10); + EXPECT_EQ(ODK_TIMER_EXPIRED, + ODK_AttemptFirstPlayback(bad_start_time, &timer_limits_, + &clock_values_, &timer_value)); + // Starting playback within the window should work. + const uint64_t start_time = + system_time(timer_limits_.earliest_playback_start_seconds); + const uint64_t cutoff_time = + system_time(timer_limits_.license_duration_seconds); + // For a soft_expiry = true, we should not set a timer. + EXPECT_EQ(ODK_DISABLE_TIMER, + ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, + &timer_value)); + EXPECT_EQ(kActive, clock_values_.status); + EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); + EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); + EXPECT_EQ(0u, clock_values_.time_when_timer_expires); +} + +TEST_F(OdkTimerSoftTest, NormalTest) { + uint64_t timer_value = 0; + const uint64_t start_time = + system_time(timer_limits_.earliest_playback_start_seconds); + const uint64_t cutoff_time = + system_time(timer_limits_.license_duration_seconds); + // For a soft_expiry = true, we should not set a timer. + EXPECT_EQ(ODK_DISABLE_TIMER, + ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, + &timer_value)); + EXPECT_EQ(kActive, clock_values_.status); + EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); + EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); + EXPECT_EQ(0u, clock_values_.time_when_timer_expires); + // Try to play before the cutoff time is valid. + const uint64_t valid_play_time = cutoff_time - 1; + // This play time is outside the rental window. We are allowed to continue + // playback after the rental window expires as long as the first decrypt is + // within the rental window. + EXPECT_LE(timer_limits_.latest_playback_start_seconds, valid_play_time); + EXPECT_EQ(OEMCrypto_SUCCESS, + ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_, + &clock_values_)); + EXPECT_EQ(kActive, clock_values_.status); + // First decrypt should NOT change. + EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); + // Last decrypt should change. + EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); + // Try to play after the cutoff time is still valid for soft expiry. + const uint64_t late_play_time = cutoff_time + 1; + EXPECT_EQ(OEMCrypto_SUCCESS, + ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_, + &clock_values_)); + EXPECT_EQ(kActive, clock_values_.status); + // First decrypt should NOT change. + EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); + // Last decrypt should NOT change because we were not allowed to decrypt. + EXPECT_EQ(late_play_time, clock_values_.time_of_last_decrypt); +} + +// This verifies that the rental window only affects the first load for the +// license, not the first load for the session. +TEST_F(OdkTimerSoftTest, Reload) { + uint64_t timer_value = 0; + // Starting playback within the window should work. + const uint64_t start_time = + system_time(timer_limits_.earliest_playback_start_seconds); + const uint64_t cutoff_time = + system_time(timer_limits_.license_duration_seconds); + // For a soft_expiry = true, we should not set a timer. + EXPECT_EQ(ODK_DISABLE_TIMER, + ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, + &timer_value)); + EXPECT_EQ(kActive, clock_values_.status); + EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); + EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); + EXPECT_EQ(0, clock_values_.time_when_timer_expires); + + // We can restart playback before the cutoff time. + const uint64_t valid_restart_time = cutoff_time - 10; + ReloadClock(valid_restart_time); + EXPECT_EQ(kActive, clock_values_.status); + + // This restart is outside the rental window. We are allowed to + // restart playback after the rental window expires as long as the first + // decrypt for the license is within the rental window. + EXPECT_LE(timer_limits_.latest_playback_start_seconds, valid_restart_time); + EXPECT_EQ(ODK_DISABLE_TIMER, + ODK_AttemptFirstPlayback(valid_restart_time, &timer_limits_, + &clock_values_, &timer_value)); + EXPECT_EQ(kActive, clock_values_.status); + EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); + EXPECT_EQ(valid_restart_time, clock_values_.time_of_last_decrypt); + EXPECT_EQ(0, clock_values_.time_when_timer_expires); + + // Try to play before the cutoff time is valid. + const uint64_t valid_play_time = cutoff_time - 1; + // This play time is outside the rental window. That means we are allowed + // to continue playback after the rental window expires as long as the first + // decrypt is within the rental window. + EXPECT_LE(timer_limits_.latest_playback_start_seconds, valid_play_time); + EXPECT_EQ(OEMCrypto_SUCCESS, + ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_, + &clock_values_)); + EXPECT_EQ(kActive, clock_values_.status); + // First decrypt should NOT change. + EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); + // Last decrypt should change. + EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); + EXPECT_EQ(0u, clock_values_.time_when_timer_expires); + // Try to play after the cutoff time is still valid. + const uint64_t late_play_time = cutoff_time + 1; + EXPECT_EQ(OEMCrypto_SUCCESS, + ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_, + &clock_values_)); + EXPECT_EQ(kActive, clock_values_.status); + // First decrypt should NOT change. + EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); + // Last decrypt should change. + EXPECT_EQ(late_play_time, clock_values_.time_of_last_decrypt); + EXPECT_EQ(0u, clock_values_.time_when_timer_expires); +} + +TEST_F(OdkTimerSoftTest, ReloadLate) { + uint64_t timer_value = 0; + // Starting playback within the window should work. + const uint64_t start_time = + system_time(timer_limits_.earliest_playback_start_seconds); + const uint64_t cutoff_time = + system_time(timer_limits_.license_duration_seconds); + // For a soft_expiry = true, we should not set a timer. + EXPECT_EQ(ODK_DISABLE_TIMER, + ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, + &timer_value)); + EXPECT_EQ(kActive, clock_values_.status); + EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); + EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); + EXPECT_EQ(0u, clock_values_.time_when_timer_expires); + + // We can not restart playback after the cutoff time. + const uint64_t late_restart_time = cutoff_time + 10; + ReloadClock(late_restart_time); + EXPECT_EQ(kActive, clock_values_.status); + EXPECT_EQ(ODK_TIMER_EXPIRED, + ODK_AttemptFirstPlayback(late_restart_time, &timer_limits_, + &clock_values_, &timer_value)); + EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); + EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); + + // Calling UpdateLastPlaybackTimer should not be done, but if it were done, + // it should also fail. + EXPECT_EQ(ODK_TIMER_EXPIRED, + ODK_UpdateLastPlaybackTime(late_restart_time, &timer_limits_, + &clock_values_)); + // First decrypt should NOT change. + EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); + // Last decrypt should NOT change because we were not allowed to decrypt. + EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); +} +// ************************************************************************ + +// ************************************************************************ +// From the server's point of view, there are two flags that decide soft or +// hard limits: the rental duration, and the playback duration. From +// OEMCrypto's point of view, there is only playback duration. A soft or hard +// rental duration is translated into different rental and license durations. +// The four test classes below all have a 700 rental window and a 200 playback +// duration. We'll use the server description, and then set the OEMCrypto +// restraints in the test SetUp() function. Note, it's easier to use the word +// "day" but really the rental window is 700 seconds, not 7 days. These tests +// have some coverage overlap with the ones above, but it is better to have +// these all grouped together to make sure we cover all of the server team's +// needs. +// ************************************************************************ +class Odk7DayTest : public OdkTimerRentalWindow, + public WithParamInterface { + public: + Odk7DayTest() { + rental_window_duration_ = 700; + rental_window_start_ = 100u; + } + + protected: + void SetUp() override { + OdkTimerRentalWindow::SetUp(); + server_expiry_ = GetParam(); + const uint64_t playback_duration = 200; + // Beginning of rental window. it is unusual to start it in the future, + // but it is a supported use case, so we'll test it here. + timer_limits_.earliest_playback_start_seconds = rental_window_start_; + // The rental window is 700 long. + timer_limits_.latest_playback_start_seconds = + timer_limits_.earliest_playback_start_seconds + rental_window_duration_; + // Playback duration is 200 starting from first playback. + timer_limits_.initial_playback_duration_seconds = playback_duration; + if (server_expiry_.soft_rental) { + // The license duration limits any restart. For soft rental window, the + // server will set this to the rental end + playback duration. + // License duration is in seconds since license signed. + timer_limits_.license_duration_seconds = + timer_limits_.latest_playback_start_seconds + playback_duration; + } else { + // The license duration limits any restart. For hard rental window, the + // server will set this to the rental end. + // License duration is in seconds since license signed. + timer_limits_.license_duration_seconds = + timer_limits_.latest_playback_start_seconds; + } + timer_limits_.soft_expiry = server_expiry_.soft_playback; + } + ServerExpiry server_expiry_; +}; + +TEST_P(Odk7DayTest, StartDay3) { + uint64_t timer_value = 0; + // Starting playback within the window should work. + const uint64_t three_days = 300u; + const uint64_t start_time = system_time(rental_window_start_ + three_days); + const uint64_t cutoff_time = + start_time + timer_limits_.initial_playback_duration_seconds; + if (server_expiry_.soft_playback) { + // If the playback expiry is soft, then the timer is disabled. + EXPECT_EQ(ODK_DISABLE_TIMER, + ODK_AttemptFirstPlayback(start_time, &timer_limits_, + &clock_values_, &timer_value)); + } else { + // If the playback expiry is hard, then the timer should be set. + EXPECT_EQ(ODK_SET_TIMER, + ODK_AttemptFirstPlayback(start_time, &timer_limits_, + &clock_values_, &timer_value)); + EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); + EXPECT_NEAR(cutoff_time - start_time, timer_value, kTolerance); + } + EXPECT_EQ(kActive, clock_values_.status); + EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); + EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); + // Try to play before the cutoff time is valid. + const uint64_t valid_play_time = cutoff_time - 50; + EXPECT_EQ(OEMCrypto_SUCCESS, + ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_, + &clock_values_)); + EXPECT_EQ(kActive, clock_values_.status); + // First decrypt should NOT change. + EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); + // Last decrypt should change. + EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); + if (!server_expiry_.soft_playback) { + EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); + } +} + +TEST_P(Odk7DayTest, StartDay3Reload) { + uint64_t timer_value = 0; + // Starting playback within the window should work. + const uint64_t three_days = 300u; + const uint64_t start_time = system_time(rental_window_start_ + three_days); + const uint64_t cutoff_time = + start_time + timer_limits_.initial_playback_duration_seconds; + EXPECT_NE(ODK_TIMER_EXPIRED, + ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, + &timer_value)); + // ----------------------------------------------------------------------- + // Try to reload and play before the cutoff time is valid. + uint64_t valid_restart_time = cutoff_time - 10; + ReloadClock(valid_restart_time); + EXPECT_EQ(kActive, clock_values_.status); + if (server_expiry_.soft_playback) { + // If the playback expiry is soft, then the timer is disabled. + EXPECT_EQ(ODK_DISABLE_TIMER, + ODK_AttemptFirstPlayback(valid_restart_time, &timer_limits_, + &clock_values_, &timer_value)); + } else { + // If the playback expiry is hard, then the timer should be set. + EXPECT_EQ(ODK_SET_TIMER, + ODK_AttemptFirstPlayback(valid_restart_time, &timer_limits_, + &clock_values_, &timer_value)); + EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); + EXPECT_NEAR(cutoff_time - valid_restart_time, timer_value, kTolerance); + } + + // Try to play before the cutoff time is valid. + const uint64_t valid_play_time = cutoff_time - 1; + EXPECT_EQ(OEMCrypto_SUCCESS, + ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_, + &clock_values_)); + EXPECT_EQ(kActive, clock_values_.status); + // First decrypt should NOT change. + EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); + // Last decrypt should change. + EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); + if (!server_expiry_.soft_playback) { + EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); + } + + // Try to play after the cutoff time is valid for soft expiry, but not hard. + const uint64_t late_play_time = cutoff_time + 1; + if (server_expiry_.soft_playback) { + EXPECT_EQ(OEMCrypto_SUCCESS, + ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_, + &clock_values_)); + // Last decrypt should change because we were allowed to decrypt. + EXPECT_EQ(late_play_time, clock_values_.time_of_last_decrypt); + } else { + EXPECT_EQ(ODK_TIMER_EXPIRED, + ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_, + &clock_values_)); + // Last decrypt should NOT change because we were not allowed to decrypt. + EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); + EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); + } + // First decrypt should NOT change for either soft or hard. + EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); + + // ----------------------------------------------------------------------- + // Try to reload after the cutoff time is not valid for soft or hard + // playback expiry. + const uint64_t late_restart_time = cutoff_time + 1; + ReloadClock(late_restart_time); + EXPECT_EQ(kActive, clock_values_.status); + EXPECT_EQ(ODK_TIMER_EXPIRED, + ODK_AttemptFirstPlayback(late_restart_time, &timer_limits_, + &clock_values_, &timer_value)); + EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); + + // Calling UpdateLastPlaybackTimer should not be done, but if it were done, + // it should also fail. + EXPECT_EQ(ODK_TIMER_EXPIRED, + ODK_UpdateLastPlaybackTime(late_restart_time, &timer_limits_, + &clock_values_)); + // First decrypt should NOT change. + EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); + // Last decrypt should not change. + if (server_expiry_.soft_playback) { + EXPECT_EQ(late_play_time, clock_values_.time_of_last_decrypt); + } else { + EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); + } +} + +TEST_P(Odk7DayTest, StartDay6Reload) { + uint64_t timer_value = 0; + // Starting playback within the window should work. + const uint64_t six_days = 600u; + const uint64_t start_time = system_time(rental_window_start_ + six_days); + const uint64_t cutoff_time = + server_expiry_.soft_rental + ? + // If the rental expiry is soft, we can continue playing and + // reloading for the full playback duration. + start_time + timer_limits_.initial_playback_duration_seconds + // If the rental expiry is hard, we can reload only within the + // rental window. + : system_time(timer_limits_.latest_playback_start_seconds); + // Let's double check that math: + if (server_expiry_.soft_rental) { + // If that was not clear, the cutoff = 100 start of window + 600 start time + // + 200 playback duration... + EXPECT_EQ(system_time(900u), cutoff_time); + } else { + // ...and for hard rental, the cutoff = 100 start of window + 700 rental + // duration. + EXPECT_EQ(system_time(800u), cutoff_time); + } + + if (server_expiry_.soft_playback) { + // If the playback expiry is soft, then the timer is disabled. + EXPECT_EQ(ODK_DISABLE_TIMER, + ODK_AttemptFirstPlayback(start_time, &timer_limits_, + &clock_values_, &timer_value)); + } else { + // If the playback expiry is hard, then the timer should be set. + EXPECT_EQ(ODK_SET_TIMER, + ODK_AttemptFirstPlayback(start_time, &timer_limits_, + &clock_values_, &timer_value)); + EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); + EXPECT_NEAR(cutoff_time - start_time, timer_value, kTolerance); + } + EXPECT_EQ(kActive, clock_values_.status); + EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); + EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); + // Try to play before the cutoff time is valid. + uint64_t valid_play_time = cutoff_time - 50; + EXPECT_EQ(OEMCrypto_SUCCESS, + ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_, + &clock_values_)); + EXPECT_EQ(kActive, clock_values_.status); + // First decrypt should NOT change. + EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); + // Last decrypt should change. + EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); + if (!server_expiry_.soft_playback) { + EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); + } + + // ----------------------------------------------------------------------- + // Try to reload and play before the cutoff time is valid. + uint64_t valid_restart_time = cutoff_time - 10; + ReloadClock(valid_restart_time); + EXPECT_EQ(kActive, clock_values_.status); + if (server_expiry_.soft_playback) { + // If the playback expiry is soft, then the timer is disabled. + EXPECT_EQ(ODK_DISABLE_TIMER, + ODK_AttemptFirstPlayback(valid_restart_time, &timer_limits_, + &clock_values_, &timer_value)); + } else { + // If the playback expiry is hard, then the timer should be set. + EXPECT_EQ(ODK_SET_TIMER, + ODK_AttemptFirstPlayback(valid_restart_time, &timer_limits_, + &clock_values_, &timer_value)); + EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); + EXPECT_NEAR(cutoff_time - valid_restart_time, timer_value, kTolerance); + } + + // Try to play before the cutoff time is valid. + valid_play_time = cutoff_time - 1; + EXPECT_EQ(OEMCrypto_SUCCESS, + ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_, + &clock_values_)); + EXPECT_EQ(kActive, clock_values_.status); + // First decrypt should NOT change. + EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); + // Last decrypt should change. + EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); + if (!server_expiry_.soft_playback) { + EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); + } + + // Try to play after the cutoff time is valid for soft expiry, but not hard. + const uint64_t late_play_time = cutoff_time + 1; + if (server_expiry_.soft_playback) { + EXPECT_EQ(OEMCrypto_SUCCESS, + ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_, + &clock_values_)); + // Last decrypt should change because we were allowed to decrypt. + EXPECT_EQ(late_play_time, clock_values_.time_of_last_decrypt); + } else { + EXPECT_EQ(ODK_TIMER_EXPIRED, + ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_, + &clock_values_)); + // Last decrypt should NOT change because we were not allowed to decrypt. + EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); + EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); + } + // First decrypt should NOT change for either soft or hard. + EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); + + // ----------------------------------------------------------------------- + // Try to reload after the cutoff time is not valid for soft or hard + // playback expiry. + const uint64_t late_restart_time = cutoff_time + 2; + ReloadClock(late_restart_time); + EXPECT_EQ(kActive, clock_values_.status); + EXPECT_EQ(ODK_TIMER_EXPIRED, + ODK_AttemptFirstPlayback(late_restart_time, &timer_limits_, + &clock_values_, &timer_value)); + EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); + + // Calling UpdateLastPlaybackTimer should not be done, but if it were done, + // it should also fail. + EXPECT_EQ(ODK_TIMER_EXPIRED, + ODK_UpdateLastPlaybackTime(late_restart_time, &timer_limits_, + &clock_values_)); + // First decrypt should NOT change. + EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); + // Last decrypt should not change. + if (server_expiry_.soft_playback) { + EXPECT_EQ(late_play_time, clock_values_.time_of_last_decrypt); + } else { + EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); + } +} + +// This test explicitly shows the difference between hard and soft rental +// expiry. This is not an OEMCrypto concept, but it is used on the serer, and +// this test verifies the logic is handled correctly when we translate it into +// OEMCrypto concepts. +TEST_P(Odk7DayTest, StartDay6ReloadDay7) { + uint64_t timer_value = 0; + // Starting playback within the window should work. + const uint64_t six_days = 600u; + const uint64_t seven_days = 700u; + const uint64_t start_time = system_time(rental_window_start_ + six_days); + EXPECT_NE(ODK_TIMER_EXPIRED, + ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, + &timer_value)); + + // Reload time is 1 second after end of rental window. + const uint64_t restart_time = + system_time(timer_limits_.latest_playback_start_seconds + 1); + ReloadClock(restart_time); + if (server_expiry_.soft_rental) { + if (server_expiry_.soft_playback) { + // If the playback expiry is soft, then the timer is disabled. + EXPECT_EQ(ODK_DISABLE_TIMER, + ODK_AttemptFirstPlayback(restart_time, &timer_limits_, + &clock_values_, &timer_value)); + } else { + const uint64_t cutoff_time = + start_time + timer_limits_.initial_playback_duration_seconds; + // If the playback expiry is hard, then the timer should be set. + EXPECT_EQ(ODK_SET_TIMER, + ODK_AttemptFirstPlayback(restart_time, &timer_limits_, + &clock_values_, &timer_value)); + EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, + kTolerance); + EXPECT_NEAR(cutoff_time - restart_time, timer_value, kTolerance); + } + } else { + // For hard rental expiry, reloading after the rental window fails. + EXPECT_EQ(ODK_TIMER_EXPIRED, + ODK_AttemptFirstPlayback(restart_time, &timer_limits_, + &clock_values_, &timer_value)); + } +} + +TEST_P(Odk7DayTest, StartDay8) { + uint64_t timer_value = 0; + // Starting playback after the rental window should not work. + const uint64_t eight_days = 800u; + const uint64_t start_time = system_time(rental_window_start_ + eight_days); + EXPECT_EQ(ODK_TIMER_EXPIRED, + ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, + &timer_value)); + EXPECT_EQ(kUnused, clock_values_.status); + EXPECT_EQ(0u, clock_values_.time_of_first_decrypt); + EXPECT_EQ(0u, clock_values_.time_of_last_decrypt); + // Calling UpdateLastPlaybackTimer should not be done, but if it were done, + // it should also fail. + EXPECT_EQ(ODK_TIMER_EXPIRED, ODK_UpdateLastPlaybackTime( + start_time, &timer_limits_, &clock_values_)); + EXPECT_EQ(kUnused, clock_values_.status); + EXPECT_EQ(0u, clock_values_.time_of_first_decrypt); + EXPECT_EQ(0u, clock_values_.time_of_last_decrypt); +} + +INSTANTIATE_TEST_CASE_P(OdkSoftHard, Odk7DayTest, + Values(ServerExpiry({true, true}), + ServerExpiry({true, false}), + ServerExpiry({false, true}), + ServerExpiry({false, false}))); + +// ************************************************************************ + +// ************************************************************************ +// TODO(b/140765031): Cover all tests in Use Cases document. +// Limited Duration License +// 7 day with renewal. +// Streaming with renewal +// Persistent with renewal + +} // namespace odk_test