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:
56
libwvdrmengine/oemcrypto/odk/Android.bp
Normal file
56
libwvdrmengine/oemcrypto/odk/Android.bp
Normal 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",
|
||||
],
|
||||
|
||||
}
|
||||
153
libwvdrmengine/oemcrypto/odk/include/OEMCryptoCENCCommon.h
Normal file
153
libwvdrmengine/oemcrypto/odk/include/OEMCryptoCENCCommon.h
Normal 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_
|
||||
583
libwvdrmengine/oemcrypto/odk/include/odk.h
Normal file
583
libwvdrmengine/oemcrypto/odk/include/odk.h
Normal 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_
|
||||
27
libwvdrmengine/oemcrypto/odk/include/odk_assert.h
Normal file
27
libwvdrmengine/oemcrypto/odk/include/odk_assert.h
Normal 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_ */
|
||||
33
libwvdrmengine/oemcrypto/odk/include/odk_overflow.h
Normal file
33
libwvdrmengine/oemcrypto/odk/include/odk_overflow.h
Normal 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_ */
|
||||
44
libwvdrmengine/oemcrypto/odk/include/odk_serialize.h
Normal file
44
libwvdrmengine/oemcrypto/odk/include/odk_serialize.h
Normal 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_ */
|
||||
96
libwvdrmengine/oemcrypto/odk/include/odk_structs.h
Normal file
96
libwvdrmengine/oemcrypto/odk/include/odk_structs.h
Normal 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 OEMCrypto’s system clock.
|
||||
*/
|
||||
typedef struct {
|
||||
uint64_t time_of_license_signed;
|
||||
uint64_t time_of_first_decrypt;
|
||||
uint64_t time_of_last_decrypt;
|
||||
uint64_t time_when_timer_expires;
|
||||
uint32_t timer_status;
|
||||
enum OEMCrypto_Usage_Entry_Status status;
|
||||
} ODK_ClockValues;
|
||||
|
||||
/*
|
||||
* ODK_NonceValues are used to match a license or provisioning request to a
|
||||
* license or provisioning response. For this reason, the api_version might be
|
||||
* lower than that supported by OEMCrypto. The api_version matches the version
|
||||
* of the license. Similarly the nonce and session_id match the session that
|
||||
* generated the license request. For an offline license, these might not match
|
||||
* the session that is loading the license. We use the nonce to prevent a
|
||||
* license from being replayed. By also including a session_id in the license
|
||||
* request and license response, we prevent an attack using the birthday paradox
|
||||
* to generate nonce collisions on a single device.
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t api_version;
|
||||
uint32_t nonce;
|
||||
uint32_t session_id;
|
||||
} ODK_NonceValues;
|
||||
|
||||
#endif // ODK_STRUCTS_H_
|
||||
54
libwvdrmengine/oemcrypto/odk/include/odk_structs_priv.h
Normal file
54
libwvdrmengine/oemcrypto/odk/include/odk_structs_priv.h
Normal 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_
|
||||
91
libwvdrmengine/oemcrypto/odk/include/serialization_base.h
Normal file
91
libwvdrmengine/oemcrypto/odk/include/serialization_base.h
Normal 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_
|
||||
172
libwvdrmengine/oemcrypto/odk/kdo/include/oec_util.h
Normal file
172
libwvdrmengine/oemcrypto/odk/kdo/include/oec_util.h
Normal 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_
|
||||
59
libwvdrmengine/oemcrypto/odk/kdo/include/oec_util_proto.h
Normal file
59
libwvdrmengine/oemcrypto/odk/kdo/include/oec_util_proto.h
Normal 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_
|
||||
209
libwvdrmengine/oemcrypto/odk/kdo/src/oec_util.cpp
Normal file
209
libwvdrmengine/oemcrypto/odk/kdo/src/oec_util.cpp
Normal 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
|
||||
161
libwvdrmengine/oemcrypto/odk/kdo/src/oec_util_proto.cpp
Normal file
161
libwvdrmengine/oemcrypto/odk/kdo/src/oec_util_proto.cpp
Normal 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
|
||||
316
libwvdrmengine/oemcrypto/odk/src/odk.c
Normal file
316
libwvdrmengine/oemcrypto/odk/src/odk.c
Normal 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;
|
||||
}
|
||||
38
libwvdrmengine/oemcrypto/odk/src/odk_overflow.c
Normal file
38
libwvdrmengine/oemcrypto/odk/src/odk_overflow.c
Normal 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;
|
||||
}
|
||||
209
libwvdrmengine/oemcrypto/odk/src/odk_serialize.c
Normal file
209
libwvdrmengine/oemcrypto/odk/src/odk_serialize.c
Normal 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);
|
||||
}
|
||||
229
libwvdrmengine/oemcrypto/odk/src/odk_timer.c
Normal file
229
libwvdrmengine/oemcrypto/odk/src/odk_timer.c
Normal 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;
|
||||
}
|
||||
205
libwvdrmengine/oemcrypto/odk/src/serialization_base.c
Normal file
205
libwvdrmengine/oemcrypto/odk/src/serialization_base.c
Normal 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); }
|
||||
223
libwvdrmengine/oemcrypto/odk/test/odk_fuzz.cpp
Normal file
223
libwvdrmengine/oemcrypto/odk/test/odk_fuzz.cpp
Normal 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;
|
||||
}
|
||||
684
libwvdrmengine/oemcrypto/odk/test/odk_test.cpp
Normal file
684
libwvdrmengine/oemcrypto/odk/test/odk_test.cpp
Normal 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);
|
||||
}
|
||||
64
libwvdrmengine/oemcrypto/odk/test/odk_test.h
Normal file
64
libwvdrmengine/oemcrypto/odk/test/odk_test.h
Normal 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_
|
||||
936
libwvdrmengine/oemcrypto/odk/test/odk_timer_test.cpp
Normal file
936
libwvdrmengine/oemcrypto/odk/test/odk_timer_test.cpp
Normal 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
|
||||
Reference in New Issue
Block a user