odk: core serialization structs & functions

odk directory copied from wvgerrit.
branch oemcrypto-v16
commit 0c9a7dc

Bug: 140758896
Test: odk_test
Change-Id: I0c631f771b794468a63e4395f6b9c3b60a1dfd4f
This commit is contained in:
Robert Shih
2019-09-12 23:31:31 -07:00
parent 9ea47dc64a
commit 2443fe807a
22 changed files with 4642 additions and 0 deletions

View File

@@ -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]/<name>/<name>
// - 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",
],
}

View File

@@ -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 <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#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_

View File

@@ -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 <stdint.h>
#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_

View File

@@ -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 <assert.h>
# 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_ */

View File

@@ -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_ */

View File

@@ -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_ */

View File

@@ -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 <stdint.h>
#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 OEMCryptos 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_

View File

@@ -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 <stdint.h>
#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_

View File

@@ -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 <stddef.h>
#include <stdint.h>
#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_

View File

@@ -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 <cstdint>
#include <string>
#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_

View File

@@ -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 <cstdint>
#include <string>
#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_

View File

@@ -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 <stdint.h>
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <string>
#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 <typename S, typename T, typename U>
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<const uint8_t*>(oemcrypto_core_message.c_str());
size_t buf_length = oemcrypto_core_message.size();
Message* msg = NULL;
AllocateMessage(&msg, message_block);
InitMessage(msg, const_cast<uint8_t*>(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 <typename T, typename S, typename P>
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<ODK_CoreMessage*>(&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<const char*>(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<const char*>(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<ODK_ParsedLicense*>(&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<ODK_ParsedProvisioning*>(&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

View File

@@ -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 <stdint.h>
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <string>
#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

View File

@@ -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 <stddef.h>
#include <stdint.h>
#include <string.h>
#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;
}

View File

@@ -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 <stddef.h>
#include <stdint.h>
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;
}

View File

@@ -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);
}

View File

@@ -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 <stdint.h>
#include <string.h>
#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;
}

View File

@@ -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 <stddef.h>
#include <stdint.h>
#include <string.h>
#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); }

View File

@@ -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 <cassert>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <functional>
#include <memory>
#include <string>
#include "OEMCryptoCENCCommon.h"
#include "odk.h"
#include "odk_serialize.h"
#include "oec_util.h"
using namespace std;
using namespace oec_util;
typedef std::function<size_t(const uint8_t*, uint8_t*, size_t)> 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<const uint8_t*>(device_id.data()), device_id.size());
}
template <typename T, typename F, typename G>
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<const char*>(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<uint8_t*>(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<const char*>(args->device_id),
args->device_id_length)};
return CreateCoreProvisioningResponse(parsed_prov, core_request,
oemcrypto_core_message);
}
template <typename A, typename T, typename F, typename G>
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<A> _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<uint8_t> _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<ODK_LicenseRequest>(ParseLicenseRequest, odk_fun_LicenseRequest));
verify_roundtrip(
data, size,
kdo_odk<ODK_RenewalRequest>(ParseRenewalRequest, odk_fun_RenewalRequest));
verify_roundtrip(data, size,
kdo_odk<ODK_ProvisioningRequest>(
ParseProvisioningRequest, odk_fun_ProvisioningRequest));
verify_roundtrip(data, size,
odk_kdo<ODK_ParseLicense_Args, ODK_ParsedLicense>(
odk_fun_LicenseResponse, kdo_fun_LicenseResponse));
verify_roundtrip(data, size,
odk_kdo<ODK_ParseRenewal_Args, ODK_RenewalMessage>(
odk_fun_RenewalResponse, kdo_fun_RenewalResponse));
verify_roundtrip(
data, size,
odk_kdo<ODK_ParseProvisioning_Args, ODK_ParsedProvisioning>(
odk_fun_ProvisioningResponse, kdo_fun_ProvisioningResponse));
return 0;
}

View File

@@ -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 <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <fstream>
#include <functional>
#include <iostream>
#include <string>
#include <vector>
#include <endian.h>
#include <gtest/gtest.h>
#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<uint32_t*>(field->value));
memcpy(buf, &u32, sizeof(u32));
break;
}
case ODK_UINT64: {
uint64_t u64 = htobe64(*static_cast<uint64_t*>(field->value));
memcpy(buf, &u64, sizeof(u64));
break;
}
case ODK_SUBSTRING: {
OEMCrypto_Substring* s = static_cast<OEMCrypto_Substring*>(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<uint8_t*>(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<uint32_t*>(field->value);
*u32p = be32toh(*u32p);
break;
}
case ODK_UINT64: {
memcpy(field->value, buf, sizeof(uint64_t));
uint64_t* u64p = static_cast<uint64_t*>(field->value);
*u64p = be64toh(*u64p);
break;
}
case ODK_SUBSTRING: {
OEMCrypto_Substring* s = static_cast<OEMCrypto_Substring*>(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<uint8_t*>(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<ODK_Field>& 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<uintptr_t>(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<ODK_Field>& fields) {
return ODK_IterFields(ODK_READ, const_cast<uint8_t*>(buf), size_in, size_out,
fields);
}
OEMCryptoResult ODK_WriteFields(uint8_t* const buf, const size_t size_in,
size_t* size_out,
std::vector<ODK_Field>& 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 <typename T, typename F, typename G>
void ValidateRequest(uint32_t message_type,
std::vector<ODK_Field>& 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<ODK_Field> 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<char*>(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 <typename T, typename F, typename G>
void ValidateResponse(uint32_t message_type,
std::vector<ODK_Field>& 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<ODK_Field> 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<ODK_Field> 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<ODK_Field> fields(n);
std::srand(0);
size_t total_size = 0;
for (int i = 0; i < n; i++) {
fields[i].type = static_cast<ODK_FieldType>(std::rand() %
static_cast<int>(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<ODK_Field> 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_LicenseRequest>(ODK_License_Request_Type, empty,
odk_prepare_func, kdo_parse_func);
}
TEST(OdkTest, RenewalRequest) {
uint64_t system_time_seconds = 0xBADDCAFE000FF1CE;
std::vector<ODK_Field> 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_RenewalRequest>(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<ODK_Field> 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_ProvisioningRequest>(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<ODK_Field> 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_LicenseRequest>(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<ODK_Field> 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_RenewalRequest>(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<ODK_Field> 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<char*>(device_id),
device_id_length);
return CreateCoreProvisioningResponse(parsed_response, core_request,
oemcrypto_core_message);
};
ValidateResponse<ODK_ProvisioningRequest>(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);
}

View File

@@ -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_

View File

@@ -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 <gtest/gtest.h>
#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<ServerExpiry> {
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