OEMCrypto v16.1 Documentation and Headers
This commit contains the updated v16.1 documentation dated Nov 12th, as well has the headers and update ODK library. Unit tests and reference code is partially implemented, but not yet complete.
This commit is contained in:
Binary file not shown.
BIN
docs/OEMCrypto_State_Diagrams.pdf
Normal file
BIN
docs/OEMCrypto_State_Diagrams.pdf
Normal file
Binary file not shown.
BIN
docs/WidevineModularDRMSecurityIntegrationGuideforCENC_v16.pdf
Normal file
BIN
docs/WidevineModularDRMSecurityIntegrationGuideforCENC_v16.pdf
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -187,15 +187,12 @@ OEMCryptoResult Level3_QueryKeyControl(OEMCrypto_SESSION session,
|
||||
size_t key_id_length,
|
||||
uint8_t* key_control_block,
|
||||
size_t* key_control_block_length);
|
||||
OEMCryptoResult Level3_DecryptCENC(OEMCrypto_SESSION session,
|
||||
const uint8_t *data_addr,
|
||||
size_t data_length,
|
||||
bool is_encrypted,
|
||||
const uint8_t *iv,
|
||||
size_t block_offset,
|
||||
OEMCrypto_DestBufferDesc* out_buffer,
|
||||
const OEMCrypto_CENCEncryptPatternDesc* pattern,
|
||||
uint8_t subsample_flags);
|
||||
OEMCryptoResult Level3_DecryptCENC(
|
||||
OEMCrypto_SESSION session, const uint8_t* data_addr, size_t data_length,
|
||||
bool is_encrypted, const uint8_t* iv, size_t block_offset,
|
||||
OEMCrypto_DestBufferDesc* out_buffer_descriptor,
|
||||
const OEMCrypto_CENCEncryptPatternDesc_V15* pattern,
|
||||
uint8_t subsample_flags);
|
||||
OEMCryptoResult Level3_InstallKeyboxOrOEMCert(const uint8_t* rot,
|
||||
size_t rotLength);
|
||||
OEMCryptoResult Level3_IsKeyboxOrOEMCertValid(void);
|
||||
@@ -371,11 +368,10 @@ OEMCryptoResult Level3_RefreshKeys(
|
||||
OEMCryptoResult Level3_LoadEntitledContentKeys(
|
||||
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
|
||||
size_t num_keys, const OEMCrypto_EntitledContentKeyObject* key_array);
|
||||
OEMCryptoResult Level3_CopyBuffer(OEMCrypto_SESSION session,
|
||||
const uint8_t *data_addr,
|
||||
size_t data_length,
|
||||
OEMCrypto_DestBufferDesc* out_buffer,
|
||||
uint8_t subsample_flags);
|
||||
OEMCryptoResult Level3_CopyBuffer(
|
||||
OEMCrypto_SESSION session, const uint8_t* data_addr, size_t data_length,
|
||||
const OEMCrypto_DestBufferDesc* out_buffer_descriptor,
|
||||
uint8_t subsample_flags);
|
||||
|
||||
// The following are specific to Google's Level 3 implementation and are not
|
||||
// required.
|
||||
|
||||
@@ -23,34 +23,47 @@ typedef struct WidevineKeybox { // 128 bytes total.
|
||||
uint8_t crc_[4];
|
||||
} WidevineKeybox;
|
||||
|
||||
/*
|
||||
* SRM_Restriction_Data
|
||||
*
|
||||
* Structure passed into LoadKeys to specify required SRM version.
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t verification[8]; // must be "HDCPDATA"
|
||||
uint32_t minimum_srm_version; // version number.
|
||||
} SRM_Restriction_Data;
|
||||
|
||||
// clang-format off
|
||||
// Key Control Block Bit Masks:
|
||||
const uint32_t kControlObserveDataPath = (1<<31);
|
||||
const uint32_t kControlObserveHDCP = (1<<30);
|
||||
const uint32_t kControlObserveCGMS = (1<<29);
|
||||
const uint32_t kControlRequireAntiRollbackHardware = (1<<28);
|
||||
const uint32_t kControlAllowHashVerification = (1<<24);
|
||||
const uint32_t kSharedLicense = (1<<23);
|
||||
const uint32_t kControlSRMVersionRequired = (1<<22);
|
||||
const uint32_t kControlDisableAnalogOutput = (1<<21);
|
||||
const uint32_t kControlSecurityPatchLevelShift = 15;
|
||||
const uint32_t kControlSecurityPatchLevelMask =
|
||||
(0x3F<<kControlSecurityPatchLevelShift);
|
||||
const uint32_t kControlReplayMask = (0x03<<13);
|
||||
const uint32_t kControlNonceRequired = (0x01<<13);
|
||||
const uint32_t kControlNonceOrEntry = (0x02<<13);
|
||||
const uint32_t kControlHDCPVersionShift = 9;
|
||||
const uint32_t kControlHDCPVersionMask = (0x0F<<kControlHDCPVersionShift);
|
||||
const uint32_t kControlAllowEncrypt = (1<<8);
|
||||
const uint32_t kControlAllowDecrypt = (1<<7);
|
||||
const uint32_t kControlAllowSign = (1<<6);
|
||||
const uint32_t kControlAllowVerify = (1<<5);
|
||||
const uint32_t kControlDataPathSecure = (1<<4);
|
||||
const uint32_t kControlNonceEnabled = (1<<3);
|
||||
const uint32_t kControlHDCPRequired = (1<<2);
|
||||
const uint32_t kControlCGMSMask = (0x03);
|
||||
const uint32_t kControlCGMSCopyFreely = (0x00);
|
||||
const uint32_t kControlCGMSCopyOnce = (0x02);
|
||||
const uint32_t kControlCGMSCopyNever = (0x03);
|
||||
const uint32_t kControlObserveDataPath = (1u << 31);
|
||||
const uint32_t kControlObserveHDCP = (1u << 30);
|
||||
const uint32_t kControlObserveCGMS = (1u << 29);
|
||||
const uint32_t kControlRequireAntiRollbackHardware = (1u << 28);
|
||||
const uint32_t kControlAllowHashVerification = (1u << 24);
|
||||
const uint32_t kSharedLicense = (1u << 23);
|
||||
const uint32_t kControlSRMVersionRequired = (1u << 22);
|
||||
const uint32_t kControlDisableAnalogOutput = (1u << 21);
|
||||
const uint32_t kControlSecurityPatchLevelShift = 15;
|
||||
const uint32_t kControlSecurityPatchLevelMask =
|
||||
(0x3Fu << kControlSecurityPatchLevelShift);
|
||||
const uint32_t kControlReplayMask = (0x03u << 13);
|
||||
const uint32_t kControlNonceRequired = (0x01u << 13);
|
||||
const uint32_t kControlNonceOrEntry = (0x02u << 13);
|
||||
const uint32_t kControlHDCPVersionShift = 9;
|
||||
const uint32_t kControlHDCPVersionMask =
|
||||
(0x0Fu << kControlHDCPVersionShift);
|
||||
const uint32_t kControlAllowEncrypt = (1u << 8);
|
||||
const uint32_t kControlAllowDecrypt = (1u << 7);
|
||||
const uint32_t kControlAllowSign = (1u << 6);
|
||||
const uint32_t kControlAllowVerify = (1u << 5);
|
||||
const uint32_t kControlDataPathSecure = (1u << 4);
|
||||
const uint32_t kControlNonceEnabled = (1u << 3);
|
||||
const uint32_t kControlHDCPRequired = (1u << 2);
|
||||
const uint32_t kControlCGMSMask = (0x03);
|
||||
const uint32_t kControlCGMSCopyFreely = (0x00);
|
||||
const uint32_t kControlCGMSCopyOnce = (0x02);
|
||||
const uint32_t kControlCGMSCopyNever = (0x03);
|
||||
// clang-format on
|
||||
|
||||
// Various constants and sizes:
|
||||
static const size_t KEY_CONTROL_SIZE = 16;
|
||||
@@ -61,7 +74,7 @@ static const size_t KEY_SIZE = 16;
|
||||
static const size_t MAC_KEY_SIZE = 32;
|
||||
static const size_t KEYBOX_KEY_DATA_SIZE = 72;
|
||||
static const size_t SRM_REQUIREMENT_SIZE = 12;
|
||||
|
||||
static const size_t HMAC_SHA256_SIGNATURE_SIZE = 32;
|
||||
|
||||
} // namespace wvoec
|
||||
|
||||
|
||||
@@ -36,9 +36,11 @@
|
||||
* ODK library shall be sanitized by the OEMCrypto implementer to prevent
|
||||
* modification by any process running the REE.
|
||||
*
|
||||
* See the document "Widevine Core Message Serialization" for a detailed
|
||||
* description of the ODK API. You can find this document in the widevine
|
||||
* repository as docs/Widevine_Core_Message_Serialization.pdf
|
||||
* 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
|
||||
*
|
||||
*********************************************************************/
|
||||
|
||||
@@ -54,215 +56,473 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
* ODK_PrepareCoreLicenseRequest
|
||||
* ODK_InitializeSessionValues
|
||||
*
|
||||
* Description
|
||||
* OEMCrypto will use ODK_PrepareCoreLicenseRequest to prepare the core
|
||||
* license request message.
|
||||
*
|
||||
* If ODK_PrepareCoreLicenseRequest returns OEMCrypto_SUCCESS, then
|
||||
* OEMCrypto shall sign the concatenation of the core message and a non-core
|
||||
* CDM protobuf message using the DRM certificate's private key. If it
|
||||
* returns an error, the error should be returned by OEMCrypto to the CDM
|
||||
* layer.
|
||||
*
|
||||
* We use nonce to prevent replay attacks.
|
||||
*
|
||||
* We use session_id and nonce in license request/response to prevent
|
||||
* birthday attacks that attemp to trigger nonce collision.
|
||||
* Description:
|
||||
* This function initializes the session's data structures. It shall be
|
||||
* called from OEMCrypto_OpenSession.
|
||||
*
|
||||
* 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_length:
|
||||
* 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] api_version: should be the same as OEMCrypto_APIVersion
|
||||
* [in] nonce: the nonce generated by OEMCrypto_GenerateNonce
|
||||
* [in] session_id: the current session id.
|
||||
* [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 success
|
||||
* OEMCrypto_ERROR_SHORT_BUFFER if buffer is too short
|
||||
* 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_length,
|
||||
uint32_t api_version, uint32_t nonce, uint32_t session_id);
|
||||
uint8_t* message, size_t message_length, size_t* core_message_size,
|
||||
const ODK_NonceValues* nonce_values);
|
||||
|
||||
/*
|
||||
* ODK_PrepareCoreRenewalRequest
|
||||
*
|
||||
* Description:
|
||||
* OEMCrypto will use ODK_PrepareCoreRenewalRequest to prepare the core
|
||||
* license renewal message.
|
||||
* 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.
|
||||
*
|
||||
* If ODK_PrepareCoreRenewalRequest returns OEMCrypto_SUCCESS, then OEMCrypto
|
||||
* signs the concatenation of the core message and a non-core CDM
|
||||
* protobuf message using the session's client renewal mac key. If it returns
|
||||
* an error, the error should be returned by OEMCrypto to the CDM layer. This
|
||||
* renewal mac key will have been delivered in the license via LoadLicense.
|
||||
* This shall be called by OEMCrypto from OEMCrypto_PrepAndSignRenewalRequest.
|
||||
*
|
||||
* The proper value of timers and clocks are discussed in the document "Timer
|
||||
* and License Renewal Updates". It is important to notice that the nonce
|
||||
* passed into the renewal message is from the original message loaded via
|
||||
* LoadLicense. A new nonce is not used for each renewal.
|
||||
* 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/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_length:
|
||||
* 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] api_version:should be the same as OEMCrypto_APIVersion
|
||||
* [in] license_nonce: the nonce from the original license.
|
||||
* [in] session_id: the current session id.
|
||||
* [in] clock_values: the sessions clock values.
|
||||
* [in] system_time_seconds: the current time on OEMCrypto's clock.
|
||||
* [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 success
|
||||
* OEMCrypto_ERROR_SHORT_BUFFER if buffer is too short
|
||||
* 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_length,
|
||||
uint32_t api_version, uint32_t license_nonce, uint32_t session_id,
|
||||
const ODK_ClockValues* clock_values, uint64_t system_time_seconds);
|
||||
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:
|
||||
* OEMCrypto will use ODK_PrepareCoreProvisioningRequest to prepare the core
|
||||
* provisioning message.
|
||||
* 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.
|
||||
*
|
||||
* If ODK_PrepareCoreProvisioningRequest returns OEMCrypto_SUCCESS, then
|
||||
* OEMCrypto shall sign the concatenation of the core message and a non-core
|
||||
* CDM protobuf message. If it returns an error, the error should be returned
|
||||
* by OEMCrypto to the CDM layer.
|
||||
* This shall be called by OEMCrypto from
|
||||
* OEMCrypto_PrepAndSignProvisioningRequest.
|
||||
*
|
||||
* For a device that has a keybox, i.e. Provisioning 2.0, OEMCrypto will sign
|
||||
* the response with the session’s derived client mac key.
|
||||
* 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.
|
||||
*
|
||||
* For a device that has an OEM Certificate, i.e. Provisioning 3.0, OEMCrypto
|
||||
* will sign the response with the private key associated with the OEM
|
||||
* Certificate.
|
||||
* 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/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_length:
|
||||
* 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] api_version: should be the same as OEMCrypto_APIVersion
|
||||
* [in] nonce: the nonce generated by OEMCrypto_GenerateNonce
|
||||
* [in] session_id: the current session id.
|
||||
* [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, at most 64 bytes.
|
||||
* [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 success
|
||||
* OEMCrypto_ERROR_SHORT_BUFFER if buffer is too short
|
||||
* 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_length,
|
||||
uint32_t api_version, uint32_t nonce, uint32_t session_id,
|
||||
const uint8_t* const device_id, uint32_t device_id_length);
|
||||
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:
|
||||
* OEMCrypto will use ODK_ParseLicense to parse and verify the license
|
||||
* response.
|
||||
* The function ODK_ParseLicense will parse the message and verify fields in
|
||||
* the message.
|
||||
*
|
||||
* ODK_ParseLicense will parse the license response and verify:
|
||||
* 1. Either the nonce matches the one passed in or
|
||||
* the license does not require a nonce.
|
||||
* 2. The API version of the license response matches.
|
||||
* 3. The session id of the license response matches.
|
||||
* If the message does not parse correctly, ODK_VerifyAndParseLicense will
|
||||
* return ODK_ERROR_CORE_MESSAGE that OEMCrypto should return to the CDM
|
||||
* layer above.
|
||||
*
|
||||
* ODK_ParseLicense will parse the message and set each OEMCrypto_Substring
|
||||
* output field to a location in the license response. If the license
|
||||
* response does not parse correctly, ODK_ParseLicense will return an error
|
||||
* that OEMCrypto should return to the CDM.
|
||||
* 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 license response message
|
||||
* [in] message_length: length of the license response
|
||||
* [in] api_version: should be the same as OEMCrypto_APIVersion
|
||||
* [in] nonce: the last nonce generated by OEMCrypto_GenerateNonce
|
||||
* [in] session_id: the current session id.
|
||||
* [in] initial_license_load:
|
||||
* true when called for OEMCrypto_LoadLicense
|
||||
* false when called for OEMCrypto_ReloadLicense
|
||||
* [in] usage_entry_present:
|
||||
* whether the session has a new usage entry associated with it created via
|
||||
* OEMCrypto_CreateNewUsageEntry
|
||||
* [in] max_num_keys:
|
||||
* the maximum size of the array key_array.
|
||||
* For many implementations, this is a compile time constant
|
||||
* [out] parsed_license: destination struct for parsed output
|
||||
* [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 success
|
||||
* ODK_ERROR_CORE_MESSAGE
|
||||
* if the license response did not parse correctly,
|
||||
* or there were other incorrect values.
|
||||
* 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,
|
||||
uint32_t api_version, uint32_t nonce,
|
||||
uint32_t session_id, bool initial_license_load,
|
||||
bool usage_entry_present, size_t max_num_keys,
|
||||
ODK_ParsedLicense* parsed_license);
|
||||
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:
|
||||
* OEMCrypto will use ODK_ParseRenewal to parse and verify the renewal
|
||||
* response.
|
||||
* 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.
|
||||
*
|
||||
* If ODK_ParseRenewal returns success, then the session's timers and clocks
|
||||
* will be updated as described in the document "Timer and License Renewal
|
||||
* Updates" and in "Widevine Modular DRM Version 16 Delta". If
|
||||
* ODK_ParseRenewal returns an error, OEMCrypto returns the error to the CDM
|
||||
* layer.
|
||||
* 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 renewal response message
|
||||
* [in] message_length: length of the renewal response
|
||||
* [in] api_version: should be the same as OEMCrypto_APIVersion
|
||||
* [in] license_nonce: the nonce from the original license.
|
||||
* [in] session_id: the current session id.
|
||||
* [in] system_time: the current time on OEMCrypto's clock.
|
||||
* [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.
|
||||
* [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 renewal response did not parse correctly,
|
||||
* or there were other incorrect values.
|
||||
* ODK_SET_TIMER Success, reset timer to the specified timer value.
|
||||
* ODK_DISABLE_TIMER Success, but disable timer. Allow Unlimited playback.
|
||||
* ODK_TIMER_EXPIRED Disable timer. Playback is not allowed.
|
||||
* 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 diabled. 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,
|
||||
uint32_t api_version, uint32_t license_nonce,
|
||||
uint32_t session_id, uint64_t system_time,
|
||||
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);
|
||||
@@ -271,36 +531,49 @@ OEMCryptoResult ODK_ParseRenewal(const uint8_t* message, size_t message_length,
|
||||
* ODK_ParseProvisioning
|
||||
*
|
||||
* Description:
|
||||
* OEMCrypto will use ODK_ParseProvisioning to parse and verify the
|
||||
* provisioning response.
|
||||
* The function ODK_ParseProvisioning will parse the message and verify the
|
||||
* nonce values match those in the license.
|
||||
*
|
||||
* After the provisioning response has been parsed, OEMCrypto does the same
|
||||
* verification and data flow as the v15 functions
|
||||
* OEMCrypto_RewrapDeviceRSAKey or OEMCrypto_RewrapDeviceRSAKey30 depending
|
||||
* on if the device has a keybox (Provisioning 2.0) or has an OEM Certificate
|
||||
* (Provisioning 3.0).
|
||||
* 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 renewal response message
|
||||
* [in] message_length: length of the renewal response
|
||||
* [in] api_version: should be the same as OEMCrypto_APIVersion
|
||||
* [in] nonce: the last nonce generated by OEMCrypto_GenerateNonce
|
||||
* [in] session_id: the current session id.
|
||||
* [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, at most 64 bytes.
|
||||
* [out] parsed_response: destination struct for parsed output
|
||||
* [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 success
|
||||
* ODK_ERROR_CORE_MESSAGE
|
||||
* if the provisioning response did not parse correctly,
|
||||
* or there were other incorrect values.
|
||||
* 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, uint32_t api_version,
|
||||
uint32_t nonce, uint32_t session_id, const uint8_t* device_id,
|
||||
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
|
||||
|
||||
@@ -17,27 +17,27 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void Pack_ODK_CoreMessage(Message* msg, ODK_CoreMessage const* obj);
|
||||
|
||||
/* 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);
|
||||
|
||||
void Unpack_ODK_CoreMessage(Message* msg, ODK_CoreMessage* obj);
|
||||
void Unpack_ODK_RenewalMessage(Message* msg, ODK_RenewalMessage* obj);
|
||||
void Unpack_ODK_ProvisioningMessage(Message* msg, ODK_ProvisioningMessage* obj);
|
||||
void Unpack_OEMCrypto_Substring(Message* msg, OEMCrypto_Substring* obj);
|
||||
void Unpack_OEMCrypto_KeyObject(Message* msg, OEMCrypto_KeyObject* obj);
|
||||
void Unpack_ODK_TimerLimits(Message* msg, ODK_TimerLimits* obj);
|
||||
void Unpack_ODK_ParsedLicense(Message* msg, ODK_ParsedLicense* obj);
|
||||
void Unpack_ODK_ParsedProvisioning(Message* msg, ODK_ParsedProvisioning* 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
|
||||
@@ -11,6 +11,8 @@
|
||||
#include "OEMCryptoCENC.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.
|
||||
@@ -22,11 +24,11 @@
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t /*boolean*/ soft_expiry;
|
||||
uint64_t earliest_playback_start_seconds;
|
||||
uint64_t latest_playback_start_seconds;
|
||||
uint64_t initial_playback_duration_seconds;
|
||||
uint64_t renewal_playback_duration_seconds;
|
||||
uint64_t license_duration_seconds;
|
||||
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;
|
||||
|
||||
/*
|
||||
@@ -37,9 +39,10 @@ typedef struct {
|
||||
OEMCrypto_Substring enc_mac_keys;
|
||||
OEMCrypto_Substring pst;
|
||||
OEMCrypto_Substring srm_restriction_data;
|
||||
uint32_t license_type;
|
||||
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;
|
||||
@@ -73,4 +76,21 @@ typedef struct {
|
||||
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_
|
||||
|
||||
@@ -11,12 +11,19 @@
|
||||
#include "OEMCryptoCENC.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;
|
||||
uint32_t api_version;
|
||||
uint32_t nonce;
|
||||
uint32_t session_id;
|
||||
ODK_NonceValues nonce_values;
|
||||
} ODK_CoreMessage;
|
||||
|
||||
typedef struct {
|
||||
@@ -31,13 +38,12 @@ typedef struct {
|
||||
typedef struct {
|
||||
ODK_CoreMessage core_message;
|
||||
uint32_t device_id_length;
|
||||
uint8_t device_id[64];
|
||||
uint8_t device_id[ODK_DEVICE_ID_LEN_MAX];
|
||||
} ODK_ProvisioningMessage;
|
||||
|
||||
typedef struct {
|
||||
ODK_CoreMessage core_message;
|
||||
ODK_ParsedLicense* parsed_license;
|
||||
size_t max_num_keys;
|
||||
} ODK_LicenseResponse;
|
||||
|
||||
typedef struct {
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* 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_timer.h
|
||||
*
|
||||
* OEMCrypto v16 Timer and Renewal Functions
|
||||
*
|
||||
*********************************************************************/
|
||||
|
||||
#ifndef ODK_TIMER_H_
|
||||
#define ODK_TIMER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include "OEMCryptoCENC.h"
|
||||
#include "odk_structs.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
Documentation to be added later. Hopefully automaically from doc.
|
||||
*/
|
||||
void ODK_InitializeClockValues(ODK_ClockValues* clock_values,
|
||||
uint64_t system_time_seconds);
|
||||
|
||||
/*
|
||||
Documentation to be added later. Hopefully automaically from doc.
|
||||
*/
|
||||
void 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);
|
||||
|
||||
/*
|
||||
Documentation to be added later. Hopefully automaically from doc.
|
||||
*/
|
||||
uint32_t ODK_AttemptFirstPlayback(uint64_t system_time_seconds,
|
||||
const ODK_TimerLimits* timer_limits,
|
||||
ODK_ClockValues* clock_values,
|
||||
uint64_t* timer_value);
|
||||
/*
|
||||
Documentation to be added later. Hopefully automaically from doc.
|
||||
*/
|
||||
OEMCryptoResult ODK_UpdateLastPlaybackTime(const ODK_TimerLimits* timer_limits,
|
||||
uint64_t system_time_seconds,
|
||||
ODK_ClockValues* clock_values);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* ODK_TIMER_H_ */
|
||||
@@ -30,7 +30,7 @@ extern "C" {
|
||||
*/
|
||||
#define AllocateMessage(msg, blk) \
|
||||
uint8_t blk[SIZE_OF_MESSAGE_STRUCT]; \
|
||||
*(msg) = (Message*)(message_block);
|
||||
*(msg) = (Message*)(blk);
|
||||
|
||||
typedef struct _Message Message;
|
||||
|
||||
@@ -39,6 +39,7 @@ 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);
|
||||
172
oemcrypto/odk/kdo/include/oec_util.h
Normal file
172
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
oemcrypto/odk/kdo/include/oec_util_proto.h
Normal file
59
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_
|
||||
16
oemcrypto/odk/kdo/oec_util.gypi
Normal file
16
oemcrypto/odk/kdo/oec_util.gypi
Normal file
@@ -0,0 +1,16 @@
|
||||
# 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.
|
||||
|
||||
{
|
||||
'sources': [
|
||||
'src/oec_util.cpp',
|
||||
'src/oec_util_proto.cpp',
|
||||
],
|
||||
'include_dirs': [
|
||||
'../src',
|
||||
'../include',
|
||||
'include',
|
||||
],
|
||||
}
|
||||
|
||||
209
oemcrypto/odk/kdo/src/oec_util.cpp
Normal file
209
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
oemcrypto/odk/kdo/src/oec_util_proto.cpp
Normal file
161
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
|
||||
@@ -15,27 +15,18 @@
|
||||
#include "odk_structs_priv.h"
|
||||
#include "serialization_base.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;
|
||||
|
||||
#define ODK_LICENSE_REQUEST_SIZE 20
|
||||
#define ODK_RENEWAL_REQUEST_SIZE 28
|
||||
#define ODK_PROVISIONING_REQUEST_SIZE 88
|
||||
|
||||
/* @ private odk functions */
|
||||
|
||||
OEMCryptoResult ODK_PrepareRequest(uint8_t* buffer, size_t buffer_length,
|
||||
size_t* core_message_length,
|
||||
uint32_t message_type, uint32_t api_version,
|
||||
uint32_t nonce, uint32_t session_id,
|
||||
ODK_CoreMessage* core_message) {
|
||||
if (!core_message_length || !core_message ||
|
||||
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;
|
||||
}
|
||||
@@ -44,7 +35,7 @@ OEMCryptoResult ODK_PrepareRequest(uint8_t* buffer, size_t buffer_length,
|
||||
AllocateMessage(&msg, message_block);
|
||||
InitMessage(msg, buffer, *core_message_length);
|
||||
*core_message = (ODK_CoreMessage){
|
||||
message_type, 0, api_version, nonce, session_id,
|
||||
message_type, 0, *nonce_values,
|
||||
};
|
||||
|
||||
switch (message_type) {
|
||||
@@ -76,10 +67,11 @@ OEMCryptoResult ODK_PrepareRequest(uint8_t* buffer, size_t buffer_length,
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
|
||||
OEMCryptoResult ODK_ParseResponse(const uint8_t* buf, size_t message_length,
|
||||
uint32_t message_type, uint32_t api_version,
|
||||
uint32_t nonce, uint32_t session_id,
|
||||
ODK_CoreMessage* const core_message) {
|
||||
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);
|
||||
@@ -106,12 +98,18 @@ OEMCryptoResult ODK_ParseResponse(const uint8_t* buf, size_t message_length,
|
||||
|
||||
if (GetStatus(msg) != MESSAGE_STATUS_OK ||
|
||||
message_type != core_message->message_type ||
|
||||
GetOffset(msg) != core_message->message_length ||
|
||||
api_version != core_message->api_version ||
|
||||
nonce != core_message->nonce || session_id != core_message->session_id) {
|
||||
GetOffset(msg) != core_message->message_length) {
|
||||
return ODK_ERROR_CORE_MESSAGE;
|
||||
}
|
||||
|
||||
if (nonce_values) {
|
||||
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 ODK_ERROR_CORE_MESSAGE;
|
||||
}
|
||||
}
|
||||
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -121,33 +119,37 @@ OEMCryptoResult ODK_ParseResponse(const uint8_t* buf, size_t message_length,
|
||||
|
||||
OEMCryptoResult ODK_PrepareCoreLicenseRequest(
|
||||
uint8_t* message, size_t message_length, size_t* core_message_length,
|
||||
uint32_t api_version, uint32_t nonce, uint32_t session_id) {
|
||||
const ODK_NonceValues* nonce_values) {
|
||||
ODK_PreparedLicense license_request = {0};
|
||||
return ODK_PrepareRequest(message, message_length, core_message_length,
|
||||
ODK_License_Request_Type, api_version, nonce,
|
||||
session_id, &license_request.core_message);
|
||||
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,
|
||||
uint32_t api_version, uint32_t license_nonce, uint32_t session_id,
|
||||
const ODK_NonceValues* nonce_values,
|
||||
const ODK_ClockValues* clock_values, uint64_t system_time_seconds) {
|
||||
ODK_RenewalMessage renewal_request = {0};
|
||||
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,
|
||||
api_version, license_nonce, session_id, &renewal_request.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,
|
||||
uint32_t api_version, uint32_t nonce, uint32_t session_id,
|
||||
const uint8_t* device_id, uint32_t device_id_length) {
|
||||
ODK_ProvisioningMessage provisioning_request = {0};
|
||||
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;
|
||||
}
|
||||
@@ -156,46 +158,82 @@ OEMCryptoResult ODK_PrepareCoreProvisioningRequest(
|
||||
memcpy(provisioning_request.device_id, device_id, device_id_length);
|
||||
}
|
||||
return ODK_PrepareRequest(message, message_length, core_message_length,
|
||||
ODK_Provisioning_Request_Type, api_version, nonce,
|
||||
session_id, &provisioning_request.core_message);
|
||||
ODK_Provisioning_Request_Type, nonce_values,
|
||||
&provisioning_request.core_message);
|
||||
}
|
||||
|
||||
/* @@ parse request functions */
|
||||
|
||||
OEMCryptoResult ODK_ParseLicense(const uint8_t* message, size_t message_length,
|
||||
uint32_t api_version, uint32_t nonce,
|
||||
uint32_t session_id, bool initial_license_load,
|
||||
bool usage_entry_present, size_t max_num_keys,
|
||||
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) {
|
||||
/* todo: check initial_license_load, usage_entry_present, and nonce_reqiured
|
||||
*/
|
||||
|
||||
if (!parsed_license) {
|
||||
if (!nonce_values || !parsed_license) {
|
||||
return ODK_ERROR_CORE_MESSAGE;
|
||||
}
|
||||
|
||||
ODK_LicenseResponse license_response = {{0}, parsed_license, max_num_keys};
|
||||
ODK_LicenseResponse license_response = {{0}, parsed_license};
|
||||
OEMCryptoResult err = ODK_ParseResponse(
|
||||
message, message_length, ODK_License_Response_Type, api_version, nonce,
|
||||
session_id, &license_response.core_message);
|
||||
message, message_length, ODK_License_Response_Type, NULL,
|
||||
&license_response.core_message);
|
||||
|
||||
if (err) {
|
||||
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) {
|
||||
nonce_values->nonce = license_response.core_message.nonce_values.nonce;
|
||||
nonce_values->session_id = license_response.core_message.nonce_values.session_id;
|
||||
return err;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
OEMCryptoResult ODK_ParseRenewal(const uint8_t* message, size_t message_length,
|
||||
uint32_t api_version, uint32_t license_nonce,
|
||||
uint32_t session_id, uint64_t system_time,
|
||||
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 (!timer_limits || !clock_values || !timer_value) {
|
||||
if (!nonce_values || !timer_limits || !clock_values || !timer_value) {
|
||||
return ODK_ERROR_CORE_MESSAGE;
|
||||
}
|
||||
|
||||
ODK_RenewalMessage renewal_response = {0};
|
||||
ODK_RenewalMessage renewal_response = {
|
||||
{0},
|
||||
};
|
||||
OEMCryptoResult err = ODK_ParseResponse(
|
||||
message, message_length, ODK_Renewal_Response_Type, api_version,
|
||||
license_nonce, session_id, &renewal_response.core_message);
|
||||
message, message_length, ODK_Renewal_Response_Type, nonce_values,
|
||||
&renewal_response.core_message);
|
||||
|
||||
if (err) {
|
||||
return err;
|
||||
@@ -236,23 +274,44 @@ OEMCryptoResult ODK_ParseRenewal(const uint8_t* message, size_t message_length,
|
||||
}
|
||||
|
||||
OEMCryptoResult ODK_ParseProvisioning(
|
||||
const uint8_t* message, size_t message_length, uint32_t api_version,
|
||||
uint32_t nonce, uint32_t session_id, const uint8_t* device_id,
|
||||
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 (!device_id || !parsed_response) {
|
||||
if (!nonce_values || !device_id || !parsed_response) {
|
||||
return ODK_ERROR_CORE_MESSAGE;
|
||||
}
|
||||
|
||||
ODK_ProvisioningResponse provisioning_response = {{0}, parsed_response};
|
||||
OEMCryptoResult err = ODK_ParseResponse(
|
||||
message, message_length, ODK_Provisioning_Response_Type, api_version,
|
||||
nonce, session_id, &provisioning_response.core_provisioning.core_message);
|
||||
ODK_ProvisioningResponse provisioning_response = {{
|
||||
{0},
|
||||
},
|
||||
parsed_response};
|
||||
if (device_id_length > ODK_DEVICE_ID_LEN_MAX) {
|
||||
return ODK_ERROR_CORE_MESSAGE;
|
||||
}
|
||||
|
||||
if (err ||
|
||||
memcmp(device_id, provisioning_response.core_provisioning.device_id,
|
||||
device_id_length)) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -11,14 +11,76 @@
|
||||
#include "odk_structs_priv.h"
|
||||
#include "serialization_base.h"
|
||||
|
||||
void Pack_ODK_CoreMessage(Message* msg, ODK_CoreMessage const* obj) {
|
||||
Pack_uint32_t(msg, (const uint32_t*)&obj->message_type);
|
||||
Pack_uint32_t(msg, (const uint32_t*)&obj->message_length);
|
||||
Pack_uint32_t(msg, (const uint32_t*)&obj->api_version);
|
||||
Pack_uint32_t(msg, (const uint32_t*)&obj->nonce);
|
||||
Pack_uint32_t(msg, (const uint32_t*)&obj->session_id);
|
||||
/* @ 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);
|
||||
}
|
||||
@@ -32,31 +94,41 @@ 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], 64);
|
||||
PackArray(msg, (const uint8_t*)&obj->device_id[0], sizeof(obj->device_id));
|
||||
}
|
||||
|
||||
void Unpack_ODK_CoreMessage(Message* msg, ODK_CoreMessage* obj) {
|
||||
Unpack_uint32_t(msg, (uint32_t*)&obj->message_type);
|
||||
Unpack_uint32_t(msg, (uint32_t*)&obj->message_length);
|
||||
Unpack_uint32_t(msg, (uint32_t*)&obj->api_version);
|
||||
Unpack_uint32_t(msg, (uint32_t*)&obj->nonce);
|
||||
Unpack_uint32_t(msg, (uint32_t*)&obj->session_id);
|
||||
if (!ValidMessage(msg)) return;
|
||||
/* @@ 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 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 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);
|
||||
}
|
||||
|
||||
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], 64);
|
||||
/* @ 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);
|
||||
}
|
||||
|
||||
void Unpack_OEMCrypto_KeyObject(Message* msg, OEMCrypto_KeyObject* obj) {
|
||||
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);
|
||||
@@ -64,7 +136,7 @@ void Unpack_OEMCrypto_KeyObject(Message* msg, OEMCrypto_KeyObject* obj) {
|
||||
Unpack_OEMCrypto_Substring(msg, (OEMCrypto_Substring*)&obj->key_control);
|
||||
}
|
||||
|
||||
void Unpack_ODK_TimerLimits(Message* msg, ODK_TimerLimits* obj) {
|
||||
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);
|
||||
@@ -73,7 +145,7 @@ void Unpack_ODK_TimerLimits(Message* msg, ODK_TimerLimits* obj) {
|
||||
Unpack_uint64_t(msg, (uint64_t*)&obj->license_duration_seconds);
|
||||
}
|
||||
|
||||
void Unpack_ODK_ParsedLicense(Message* msg, ODK_ParsedLicense* obj) {
|
||||
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);
|
||||
@@ -82,17 +154,19 @@ void Unpack_ODK_ParsedLicense(Message* msg, ODK_ParsedLicense* obj) {
|
||||
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 (size_t i = 0; i < (size_t)obj->key_array_length; i++) {
|
||||
for (uint32_t i = 0; i < obj->key_array_length; i++) {
|
||||
Unpack_OEMCrypto_KeyObject(msg, &obj->key_array[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void Unpack_ODK_ParsedProvisioning(Message* msg, ODK_ParsedProvisioning* obj) {
|
||||
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,
|
||||
@@ -101,11 +175,31 @@ void Unpack_ODK_ParsedProvisioning(Message* msg, ODK_ParsedProvisioning* obj) {
|
||||
(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(
|
||||
|
||||
@@ -8,32 +8,156 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "odk.h"
|
||||
#include "odk_timer.h"
|
||||
|
||||
void ODK_InitializeClockValues(ODK_ClockValues* clock_values,
|
||||
OEMCryptoResult ODK_InitializeClockValues(ODK_ClockValues* clock_values,
|
||||
uint64_t system_time_seconds) {
|
||||
if (clock_values == NULL) return;
|
||||
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;
|
||||
}
|
||||
|
||||
/* Stub functions. */
|
||||
void ODK_ReloadClockValues(ODK_ClockValues* clock_values,
|
||||
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) {}
|
||||
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) {}
|
||||
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;
|
||||
}
|
||||
|
||||
OEMCryptoResult ODK_UpdateLastPlaybackTime(const ODK_TimerLimits* timer_limits,
|
||||
uint64_t system_time_seconds,
|
||||
ODK_ClockValues* clock_values) {}
|
||||
/* 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;
|
||||
}
|
||||
|
||||
@@ -76,6 +76,13 @@ void PackArray(Message* message, const uint8_t* base, size_t size) {
|
||||
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);
|
||||
@@ -111,8 +118,7 @@ void Unpack_OEMCrypto_Substring(Message* msg, OEMCrypto_Substring* obj) {
|
||||
Unpack_uint32_t(msg, &length);
|
||||
if (!ValidMessage(msg)) return;
|
||||
size_t end = 0;
|
||||
if (obj->offset > msg->capacity ||
|
||||
odk_add_overflow_ux(obj->offset, obj->length, &end) ||
|
||||
if (offset > msg->capacity || odk_add_overflow_ux(offset, length, &end) ||
|
||||
end > msg->capacity) {
|
||||
msg->status = MESSAGE_STATUS_OVERFLOW_ERROR;
|
||||
return;
|
||||
|
||||
223
oemcrypto/odk/test/odk_fuzz.cpp
Normal file
223
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 "OEMCryptoCENC.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;
|
||||
}
|
||||
39
oemcrypto/odk/test/odk_fuzz.gyp
Normal file
39
oemcrypto/odk/test/odk_fuzz.gyp
Normal file
@@ -0,0 +1,39 @@
|
||||
# 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.
|
||||
|
||||
{
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'odk_fuzz',
|
||||
'type': 'executable',
|
||||
'includes': [
|
||||
'../src/odk.gypi',
|
||||
'../kdo/oec_util.gypi',
|
||||
],
|
||||
'include_dirs': [
|
||||
'../../include',
|
||||
'../include',
|
||||
'../src',
|
||||
'../kdo/include',
|
||||
],
|
||||
'cflags_cc': [
|
||||
'-std=c++11',
|
||||
'-g3',
|
||||
'-O0',
|
||||
'-fsanitize=fuzzer,address,undefined',
|
||||
'-fno-omit-frame-pointer',
|
||||
],
|
||||
'ldflags': [
|
||||
'-fPIC',
|
||||
'-fsanitize=fuzzer,address,undefined',
|
||||
],
|
||||
'sources': [
|
||||
'odk_fuzz.cpp',
|
||||
],
|
||||
'dependencies': [
|
||||
'../../../cdm/cdm.gyp:license_protocol'
|
||||
],
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
@@ -16,14 +17,15 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <bits/stdint-uintn.h>
|
||||
#include <string.h>
|
||||
#include <unistd.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) {
|
||||
@@ -212,10 +214,10 @@ void expect_eq_buf(const void* s1, const void* s2, size_t n) {
|
||||
}
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
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 F& odk_prepare_func, const G& kdo_parse_func) {
|
||||
uint32_t message_size = 0;
|
||||
uint32_t api_version = 16;
|
||||
uint32_t nonce = 0xdeadbeef;
|
||||
@@ -246,14 +248,26 @@ void ValidateRequest(uint32_t message_type,
|
||||
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));
|
||||
EXPECT_EQ(OEMCrypto_SUCCESS,
|
||||
odk_prepare_func(buf2, &bytes_written, t.api_version, t.nonce,
|
||||
t.session_id));
|
||||
EXPECT_EQ(bytes_written, message_size);
|
||||
expect_eq_buf(buf, buf2, message_size);
|
||||
|
||||
delete[] buf;
|
||||
delete[] buf2;
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
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 F& odk_parse_func, const G& kdo_prepare_func) {
|
||||
uint32_t message_size = 0;
|
||||
uint32_t api_version = 16;
|
||||
uint32_t nonce = 0xdeadbeef;
|
||||
@@ -276,10 +290,14 @@ void ValidateResponse(uint32_t message_type,
|
||||
}
|
||||
|
||||
uint8_t* buf = new uint8_t[message_size]();
|
||||
uint8_t* buf2 = 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));
|
||||
@@ -295,14 +313,13 @@ void ValidateResponse(uint32_t message_type,
|
||||
EXPECT_EQ(OEMCrypto_SUCCESS,
|
||||
odk_parse_func(buf, bytes_written, api_version, nonce, session_id));
|
||||
|
||||
// serialize odk output to buf2
|
||||
EXPECT_EQ(OEMCrypto_SUCCESS, ODK_IterFields(ODK_WRITE, buf2, SIZE_MAX,
|
||||
&bytes_written, total_fields));
|
||||
// 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, buf2, message_size);
|
||||
expect_eq_buf(buf, oemcrypto_core_message.data(), message_size);
|
||||
delete[] buf;
|
||||
delete[] buf2;
|
||||
delete[] zero;
|
||||
}
|
||||
|
||||
@@ -360,6 +377,7 @@ TEST(OdkTest, SerializeFieldsStress) {
|
||||
delete[] buf2;
|
||||
}
|
||||
|
||||
#if 0 // TODO(b/144233698): fix this.
|
||||
TEST(OdkTest, LicenseRequest) {
|
||||
std::vector<ODK_Field> empty;
|
||||
auto odk_prepare_func = [&](uint8_t* const buf, size_t* size,
|
||||
@@ -368,7 +386,9 @@ TEST(OdkTest, LicenseRequest) {
|
||||
return ODK_PrepareCoreLicenseRequest(buf, SIZE_MAX, size, api_version,
|
||||
nonce, session_id);
|
||||
};
|
||||
ValidateRequest(ODK_License_Request_Type, empty, odk_prepare_func);
|
||||
auto kdo_parse_func = ParseLicenseRequest;
|
||||
ValidateRequest<ODK_LicenseRequest>(ODK_License_Request_Type, empty,
|
||||
odk_prepare_func, kdo_parse_func);
|
||||
}
|
||||
|
||||
TEST(OdkTest, RenewalRequest) {
|
||||
@@ -384,7 +404,16 @@ TEST(OdkTest, RenewalRequest) {
|
||||
nonce, session_id, &clock_values,
|
||||
system_time_seconds);
|
||||
};
|
||||
ValidateRequest(ODK_Renewal_Request_Type, extra_fields, odk_prepare_func);
|
||||
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) {
|
||||
@@ -402,8 +431,22 @@ TEST(OdkTest, ProvisionRequest) {
|
||||
nonce, session_id, device_id,
|
||||
device_id_length);
|
||||
};
|
||||
ValidateRequest(ODK_Provisioning_Request_Type, extra_fields,
|
||||
odk_prepare_func);
|
||||
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) {
|
||||
@@ -489,9 +532,15 @@ TEST(OdkTest, LicenseResponse) {
|
||||
uint32_t api_version, uint32_t nonce,
|
||||
uint32_t session_id) {
|
||||
return ODK_ParseLicense(buf, size + 128, api_version, nonce, session_id, 0,
|
||||
0, 3, &parsed_license);
|
||||
0, &parsed_license);
|
||||
};
|
||||
ValidateResponse(ODK_License_Response_Type, extra_fields, odk_parse_func);
|
||||
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) {
|
||||
@@ -538,7 +587,13 @@ TEST(OdkTest, RenewalResponse) {
|
||||
message_playback_clock = 10;
|
||||
return OEMCrypto_SUCCESS;
|
||||
};
|
||||
ValidateResponse(ODK_Renewal_Response_Type, extra_fields, odk_parse_func);
|
||||
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) {
|
||||
@@ -564,16 +619,24 @@ TEST(OdkTest, ProvisionResponse) {
|
||||
auto odk_parse_func = [&](const uint8_t* buf, size_t size,
|
||||
uint32_t api_version, uint32_t nonce,
|
||||
uint32_t session_id) {
|
||||
OEMCryptoResult err =
|
||||
ODK_ParseProvisioning(buf, size + 16, api_version, nonce, session_id,
|
||||
device_id, device_id_length, &parsed_response);
|
||||
// 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, api_version, nonce, session_id,
|
||||
device_id, device_id_length, &parsed_response);
|
||||
return err;
|
||||
};
|
||||
ValidateResponse(ODK_Provisioning_Response_Type, extra_fields,
|
||||
odk_parse_func);
|
||||
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) {
|
||||
@@ -599,7 +662,7 @@ TEST(OdkSizeTest, RenewalRequest) {
|
||||
uint32_t api_version = 0;
|
||||
uint32_t nonce = 0;
|
||||
uint32_t session_id = 0;
|
||||
ODK_ClockValues clock_values;
|
||||
ODK_ClockValues clock_values = {};
|
||||
clock_values.time_of_first_decrypt = 10;
|
||||
uint64_t system_time_seconds = 15;
|
||||
EXPECT_EQ(OEMCrypto_ERROR_SHORT_BUFFER,
|
||||
@@ -628,8 +691,4 @@ TEST(OdkSizeTest, ProvisioningRequest) {
|
||||
size_t minimum_message_size = 5 * 4;
|
||||
EXPECT_GE(core_message_length, minimum_message_size);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -6,9 +6,33 @@
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "odk_timer.h"
|
||||
#include "odk.h"
|
||||
|
||||
TEST(OdkTimerTest, Init) {
|
||||
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);
|
||||
@@ -19,3 +43,894 @@ TEST(OdkTimerTest, Init) {
|
||||
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 descritpion, 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
|
||||
|
||||
@@ -19,11 +19,14 @@
|
||||
'type': 'executable',
|
||||
'sources': [
|
||||
'test/oemcrypto_test_main.cpp',
|
||||
'odk/kdo/src/oec_util.cpp',
|
||||
'<(platform_specific_dir)/file_store.cpp',
|
||||
'<(platform_specific_dir)/log.cpp',
|
||||
'<(util_dir)/src/platform.cpp',
|
||||
'<(util_dir)/src/rw_lock.cpp',
|
||||
'<(util_dir)/src/string_conversions.cpp',
|
||||
'<(util_dir)/test/test_sleep.cpp',
|
||||
'<(util_dir)/test/test_clock.cpp',
|
||||
],
|
||||
'includes': [
|
||||
'test/oemcrypto_unittests.gypi',
|
||||
@@ -35,22 +38,5 @@
|
||||
'<(gmock_dependency)',
|
||||
],
|
||||
},
|
||||
{
|
||||
'target_name': 'odk_tests',
|
||||
'type': 'executable',
|
||||
'includes': [
|
||||
'odk/test/odk_test.gypi',
|
||||
'odk/src/odk.gypi',
|
||||
],
|
||||
'include_dirs': [
|
||||
'include',
|
||||
'odk/include',
|
||||
'odk/src',
|
||||
],
|
||||
'dependencies': [
|
||||
'<(gtest_dependency)',
|
||||
'<(gmock_main_dependency)',
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "oemcrypto_engine_ref.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <chrono>
|
||||
#include <string.h>
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
@@ -17,6 +16,7 @@
|
||||
#include <openssl/aes.h>
|
||||
#include <openssl/err.h>
|
||||
|
||||
#include "clock.h"
|
||||
#include "keys.h"
|
||||
#include "log.h"
|
||||
#include "oemcrypto_key_ref.h"
|
||||
@@ -91,20 +91,23 @@ SessionContext* CryptoEngine::FindSession(SessionId sid) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
time_t CryptoEngine::OnlineTime() {
|
||||
int64_t CryptoEngine::OnlineTime() {
|
||||
// Use the monotonic clock for times that don't have to be stable across
|
||||
// device boots.
|
||||
std::chrono::steady_clock clock;
|
||||
return clock.now().time_since_epoch() / std::chrono::seconds(1);
|
||||
int64_t now = wvcdm::Clock().GetCurrentTime();
|
||||
static int64_t then = now;
|
||||
if (now < then) now = then;
|
||||
then = now;
|
||||
return now;
|
||||
}
|
||||
|
||||
time_t CryptoEngine::RollbackCorrectedOfflineTime() {
|
||||
int64_t CryptoEngine::RollbackCorrectedOfflineTime() {
|
||||
struct TimeInfo {
|
||||
// The max time recorded through this function call.
|
||||
time_t previous_time;
|
||||
int64_t previous_time;
|
||||
// If the wall time is rollbacked to before the previous_time, this member
|
||||
// is updated to reflect the offset.
|
||||
time_t rollback_offset;
|
||||
int64_t rollback_offset;
|
||||
// Pad the struct so that TimeInfo is a multiple of 16.
|
||||
uint8_t padding[16 - (2 * sizeof(time_t)) % 16];
|
||||
};
|
||||
@@ -135,7 +138,7 @@ time_t CryptoEngine::RollbackCorrectedOfflineTime() {
|
||||
if (!file) {
|
||||
LOGE("RollbackCorrectedOfflineTime: File open failed: %s",
|
||||
filename.c_str());
|
||||
return time(nullptr);
|
||||
return OnlineTime();
|
||||
}
|
||||
file->Read(reinterpret_cast<char*>(&encrypted_buffer[0]), sizeof(TimeInfo));
|
||||
// Decrypt the encrypted TimeInfo buffer.
|
||||
@@ -147,9 +150,9 @@ time_t CryptoEngine::RollbackCorrectedOfflineTime() {
|
||||
memcpy(&time_info, &clear_buffer[0], sizeof(TimeInfo));
|
||||
}
|
||||
|
||||
time_t current_time;
|
||||
int64_t current_time;
|
||||
// Add any time offsets in the past to the current time.
|
||||
current_time = time(nullptr) + time_info.rollback_offset;
|
||||
current_time = OnlineTime() + time_info.rollback_offset;
|
||||
if (time_info.previous_time > current_time) {
|
||||
// Time has been rolled back.
|
||||
// Update the rollback offset.
|
||||
@@ -174,7 +177,7 @@ time_t CryptoEngine::RollbackCorrectedOfflineTime() {
|
||||
if (!file) {
|
||||
LOGE("RollbackCorrectedOfflineTime: File open failed: %s",
|
||||
filename.c_str());
|
||||
return time(nullptr);
|
||||
return OnlineTime();
|
||||
}
|
||||
file->Write(reinterpret_cast<char*>(&encrypted_buffer[0]), sizeof(TimeInfo));
|
||||
|
||||
@@ -199,19 +202,19 @@ OEMCrypto_HDCP_Capability CryptoEngine::config_maximum_hdcp_capability() {
|
||||
}
|
||||
|
||||
OEMCryptoResult CryptoEngine::SetDestination(
|
||||
OEMCrypto_DestBufferDesc* out_description, size_t data_length,
|
||||
const OEMCrypto_DestBufferDesc* out_description, size_t data_length,
|
||||
uint8_t subsample_flags) {
|
||||
size_t max_length = 0;
|
||||
switch (out_description->type) {
|
||||
case OEMCrypto_BufferType_Clear:
|
||||
destination_ = out_description->buffer.clear.address;
|
||||
max_length = out_description->buffer.clear.max_length;
|
||||
max_length = out_description->buffer.clear.address_length;
|
||||
break;
|
||||
case OEMCrypto_BufferType_Secure:
|
||||
destination_ =
|
||||
reinterpret_cast<uint8_t*>(out_description->buffer.secure.handle) +
|
||||
out_description->buffer.secure.offset;
|
||||
max_length = out_description->buffer.secure.max_length -
|
||||
max_length = out_description->buffer.secure.handle_length -
|
||||
out_description->buffer.secure.offset;
|
||||
break;
|
||||
case OEMCrypto_BufferType_Direct:
|
||||
|
||||
@@ -31,6 +31,8 @@ typedef std::map<SessionId, SessionContext*> ActiveSessions;
|
||||
|
||||
class CryptoEngine {
|
||||
public:
|
||||
static const uint32_t kApiVersion = 16;
|
||||
|
||||
// This is like a factory method, except we choose which version to use at
|
||||
// compile time. It is defined in several source files. The build system
|
||||
// should choose which one to use by only linking in the correct one.
|
||||
@@ -87,9 +89,11 @@ class CryptoEngine {
|
||||
return kMaxSupportedOEMCryptoSessions;
|
||||
}
|
||||
|
||||
time_t OnlineTime();
|
||||
// System clock, measuring time in seconds.
|
||||
int64_t OnlineTime();
|
||||
|
||||
time_t RollbackCorrectedOfflineTime();
|
||||
// System clock with antirollback protection, measuring time in seconds.
|
||||
int64_t RollbackCorrectedOfflineTime();
|
||||
|
||||
// Verify that this nonce does not collide with another nonce in any session.
|
||||
virtual bool NonceCollision(uint32_t nonce);
|
||||
@@ -188,20 +192,22 @@ class CryptoEngine {
|
||||
virtual uint32_t resource_rating() { return 1; }
|
||||
|
||||
// Set destination pointer based on the output destination description.
|
||||
OEMCryptoResult SetDestination(OEMCrypto_DestBufferDesc* out_description,
|
||||
size_t data_length, uint8_t subsample_flags);
|
||||
OEMCryptoResult SetDestination(
|
||||
const OEMCrypto_DestBufferDesc* out_description, size_t data_length,
|
||||
uint8_t subsample_flags);
|
||||
|
||||
// The current destination.
|
||||
uint8_t* destination() { return destination_; }
|
||||
|
||||
// Subclasses can adjust the destination -- for use in testing.
|
||||
virtual void adjust_destination(OEMCrypto_DestBufferDesc* out_description,
|
||||
size_t data_length, uint8_t subsample_flags) {
|
||||
}
|
||||
virtual void adjust_destination(
|
||||
const OEMCrypto_DestBufferDesc* out_description, size_t data_length,
|
||||
uint8_t subsample_flags) {}
|
||||
|
||||
// Push destination buffer to output -- used by subclasses for testing.
|
||||
virtual OEMCryptoResult PushDestination(
|
||||
OEMCrypto_DestBufferDesc* out_description, uint8_t subsample_flags) {
|
||||
const OEMCrypto_DestBufferDesc* out_description,
|
||||
uint8_t subsample_flags) {
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,10 @@
|
||||
|
||||
namespace wvoec_ref {
|
||||
|
||||
WvKeybox::WvKeybox() : loaded_(false) {}
|
||||
WvKeybox::WvKeybox() : loaded_(false) {
|
||||
static std::string fake_device_id = "device_with_no_keybox";
|
||||
device_id_.assign(fake_device_id.begin(), fake_device_id.end());
|
||||
}
|
||||
|
||||
KeyboxError WvKeybox::Validate() {
|
||||
if (!loaded_) {
|
||||
|
||||
@@ -207,116 +207,55 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_GenerateNonce(OEMCrypto_SESSION session,
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
|
||||
OEMCRYPTO_API OEMCryptoResult OEMCrypto_SignLicenseRequest(
|
||||
OEMCrypto_SESSION session, const uint8_t* protobuf_message,
|
||||
size_t protobuf_message_length, uint8_t* core_message,
|
||||
OEMCRYPTO_API OEMCryptoResult OEMCrypto_PrepAndSignLicenseRequest(
|
||||
OEMCrypto_SESSION session, uint8_t* message, size_t message_length,
|
||||
size_t* core_message_length, uint8_t* signature, size_t* signature_length) {
|
||||
if (crypto_engine == nullptr) {
|
||||
LOGE("OEMCrypto_SignLicenseRequest: OEMCrypto Not Initialized.");
|
||||
LOGE("OEMCrypto Not Initialized.");
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
if (signature_length == nullptr || core_message_length == nullptr) {
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
if (*signature_length < SHA256_DIGEST_LENGTH) {
|
||||
*signature_length = SHA256_DIGEST_LENGTH;
|
||||
return OEMCrypto_ERROR_SHORT_BUFFER;
|
||||
}
|
||||
|
||||
if (protobuf_message == nullptr || protobuf_message_length == 0 ||
|
||||
signature == nullptr) {
|
||||
LOGE("[OEMCrypto_SignLicenseRequest(): OEMCrypto_ERROR_INVALID_CONTEXT]");
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
|
||||
SessionContext* session_ctx = crypto_engine->FindSession(session);
|
||||
if (session_ctx == nullptr || !session_ctx->isValid()) {
|
||||
LOGE("[OEMCrypto_SignLicenseRequest(): ERROR_INVALID_SESSION]");
|
||||
LOGE("ERROR_INVALID_SESSION");
|
||||
return OEMCrypto_ERROR_INVALID_SESSION;
|
||||
}
|
||||
|
||||
// TODO(b/135288420): Add core message functionality.
|
||||
*core_message_length = 0;
|
||||
|
||||
if (session_ctx->GenerateSignature(protobuf_message, protobuf_message_length,
|
||||
signature, signature_length, false)) {
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
return session_ctx->PrepAndSignLicenseRequest(message, message_length,
|
||||
core_message_length, signature,
|
||||
signature_length);
|
||||
}
|
||||
|
||||
OEMCRYPTO_API OEMCryptoResult OEMCrypto_SignRenewalRequest(
|
||||
OEMCrypto_SESSION session, const uint8_t* protobuf_message,
|
||||
size_t protobuf_message_length, uint8_t* core_message,
|
||||
OEMCRYPTO_API OEMCryptoResult OEMCrypto_PrepAndSignRenewalRequest(
|
||||
OEMCrypto_SESSION session, uint8_t* message, size_t message_length,
|
||||
size_t* core_message_length, uint8_t* signature, size_t* signature_length) {
|
||||
if (crypto_engine == nullptr) {
|
||||
LOGE("OEMCrypto_SignRenewalRequest: OEMCrypto Not Initialized.");
|
||||
LOGE("OEMCrypto Not Initialized.");
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
if (signature_length == nullptr || core_message_length == nullptr) {
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
if (*signature_length < SHA256_DIGEST_LENGTH) {
|
||||
*signature_length = SHA256_DIGEST_LENGTH;
|
||||
return OEMCrypto_ERROR_SHORT_BUFFER;
|
||||
}
|
||||
if (protobuf_message == nullptr || protobuf_message_length == 0 ||
|
||||
signature == nullptr) {
|
||||
LOGE("[OEMCrypto_SignRenewalRequest(): OEMCrypto_ERROR_INVALID_CONTEXT]");
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
|
||||
SessionContext* session_ctx = crypto_engine->FindSession(session);
|
||||
if (session_ctx == nullptr || !session_ctx->isValid()) {
|
||||
LOGE("[OEMCrypto_SignRenewalRequest(): ERROR_INVALID_SESSION]");
|
||||
LOGE("ERROR_INVALID_SESSION");
|
||||
return OEMCrypto_ERROR_INVALID_SESSION;
|
||||
}
|
||||
|
||||
// TODO(b/135288420): Add core message functionality.
|
||||
*core_message_length = 0;
|
||||
|
||||
if (session_ctx->GenerateSignature(protobuf_message, protobuf_message_length,
|
||||
signature, signature_length, true)) {
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
return session_ctx->PrepAndSignRenewalRequest(message, message_length,
|
||||
core_message_length, signature,
|
||||
signature_length);
|
||||
}
|
||||
|
||||
OEMCRYPTO_API OEMCryptoResult OEMCrypto_SignProvisioningRequest(
|
||||
OEMCrypto_SESSION session, const uint8_t* protobuf_message,
|
||||
size_t protobuf_message_length, uint8_t* core_message,
|
||||
OEMCRYPTO_API OEMCryptoResult OEMCrypto_PrepAndSignProvisioningRequest(
|
||||
OEMCrypto_SESSION session, uint8_t* message, size_t message_length,
|
||||
size_t* core_message_length, uint8_t* signature, size_t* signature_length) {
|
||||
if (crypto_engine == nullptr) {
|
||||
LOGE("OEMCrypto_SignProvisioningRequest: OEMCrypto Not Initialized.");
|
||||
LOGE("OEMCrypto Not Initialized.");
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
if (signature_length == nullptr || core_message_length == nullptr) {
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
if (*signature_length < SHA256_DIGEST_LENGTH) {
|
||||
*signature_length = SHA256_DIGEST_LENGTH;
|
||||
return OEMCrypto_ERROR_SHORT_BUFFER;
|
||||
}
|
||||
if (protobuf_message == nullptr || protobuf_message_length == 0 ||
|
||||
signature == nullptr) {
|
||||
LOGE("OEMCrypto_ERROR_INVALID_CONTEXT");
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
|
||||
SessionContext* session_ctx = crypto_engine->FindSession(session);
|
||||
if (session_ctx == nullptr || !session_ctx->isValid()) {
|
||||
LOGE("[OEMCrypto_SignProvisioningRequest(): ERROR_INVALID_SESSION]");
|
||||
LOGE("ERROR_INVALID_SESSION");
|
||||
return OEMCrypto_ERROR_INVALID_SESSION;
|
||||
}
|
||||
|
||||
// TODO(b/135288420): Add core message functionality.
|
||||
*core_message_length = 0;
|
||||
|
||||
if (session_ctx->GenerateSignature(protobuf_message, protobuf_message_length,
|
||||
signature, signature_length, false)) {
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
return session_ctx->PrepAndSignProvisioningRequest(
|
||||
message, message_length, core_message_length, signature,
|
||||
signature_length);
|
||||
}
|
||||
|
||||
bool RangeCheck(const uint8_t* message, uint32_t message_length,
|
||||
@@ -404,8 +343,8 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_LoadKeys(
|
||||
|
||||
OEMCRYPTO_API OEMCryptoResult OEMCrypto_LoadEntitledContentKeys(
|
||||
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
|
||||
const OEMCrypto_EntitledContentKeyObject* key_array,
|
||||
size_t key_array_length) {
|
||||
size_t key_array_length,
|
||||
const OEMCrypto_EntitledContentKeyObject* key_array) {
|
||||
if (key_array_length == 0) {
|
||||
LOGE("[OEMCrypto_LoadEntitledContentKeys(): key_array is empty.");
|
||||
return OEMCrypto_SUCCESS;
|
||||
@@ -437,7 +376,7 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_LoadEntitledContentKeys(
|
||||
}
|
||||
}
|
||||
return session_ctx->LoadEntitledContentKeys(message, message_length,
|
||||
key_array, key_array_length);
|
||||
key_array_length, key_array);
|
||||
}
|
||||
|
||||
OEMCRYPTO_API OEMCryptoResult OEMCrypto_RefreshKeys(
|
||||
@@ -584,17 +523,18 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_SelectKey(
|
||||
return session_ctx->SelectContentKey(key_id_str, cipher_mode);
|
||||
}
|
||||
|
||||
OEMCRYPTO_API OEMCryptoResult OEMCrypto_DecryptCENC(
|
||||
OEMCRYPTO_API OEMCryptoResult OEMCrypto_DecryptCENC_V15(
|
||||
OEMCrypto_SESSION session, const uint8_t* data_addr, size_t data_length,
|
||||
bool is_encrypted, const uint8_t* iv, size_t block_offset,
|
||||
OEMCrypto_DestBufferDesc* out_buffer,
|
||||
const OEMCrypto_CENCEncryptPatternDesc* pattern, uint8_t subsample_flags) {
|
||||
OEMCrypto_DestBufferDesc* out_buffer_descriptor,
|
||||
const OEMCrypto_CENCEncryptPatternDesc_V15* pattern,
|
||||
uint8_t subsample_flags) {
|
||||
if (crypto_engine == nullptr) {
|
||||
LOGE("OEMCrypto_DecryptCENC: OEMCrypto Not Initialized.");
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
if (data_addr == nullptr || data_length == 0 || iv == nullptr ||
|
||||
out_buffer == nullptr) {
|
||||
out_buffer_descriptor == nullptr) {
|
||||
LOGE("[OEMCrypto_DecryptCENC(): OEMCrypto_ERROR_INVALID_CONTEXT]");
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
@@ -605,8 +545,8 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_DecryptCENC(
|
||||
LOGE("[OEMCrypto_DecryptCENC(): OEMCrypto_ERROR_BUFFER_TOO_LARGE]");
|
||||
return OEMCrypto_ERROR_BUFFER_TOO_LARGE;
|
||||
}
|
||||
OEMCryptoResult status =
|
||||
crypto_engine->SetDestination(out_buffer, data_length, subsample_flags);
|
||||
OEMCryptoResult status = crypto_engine->SetDestination(
|
||||
out_buffer_descriptor, data_length, subsample_flags);
|
||||
if (status != OEMCrypto_SUCCESS) {
|
||||
LOGE("[OEMCrypto_DecryptCENC(): destination status: %d]", status);
|
||||
return status;
|
||||
@@ -624,21 +564,23 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_DecryptCENC(
|
||||
return OEMCrypto_ERROR_INVALID_SESSION;
|
||||
}
|
||||
|
||||
OEMCryptoResult result = session_ctx->DecryptCENC(
|
||||
OEMCryptoResult result = session_ctx->DecryptCENC_V15(
|
||||
iv, block_offset, pattern, data_addr, data_length, is_encrypted,
|
||||
crypto_engine->destination(), out_buffer->type, subsample_flags);
|
||||
crypto_engine->destination(), out_buffer_descriptor->type,
|
||||
subsample_flags);
|
||||
if (result != OEMCrypto_SUCCESS) return result;
|
||||
return crypto_engine->PushDestination(out_buffer, subsample_flags);
|
||||
return crypto_engine->PushDestination(out_buffer_descriptor, subsample_flags);
|
||||
}
|
||||
|
||||
OEMCRYPTO_API OEMCryptoResult OEMCrypto_CopyBuffer(
|
||||
OEMCrypto_SESSION session, const uint8_t* data_addr, size_t data_length,
|
||||
OEMCrypto_DestBufferDesc* out_buffer, uint8_t subsample_flags) {
|
||||
const OEMCrypto_DestBufferDesc* out_buffer_descriptor,
|
||||
uint8_t subsample_flags) {
|
||||
if (crypto_engine == nullptr) {
|
||||
LOGE("OEMCrypto_CopyBuffer: OEMCrypto Not Initialized.");
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
if (data_addr == nullptr || out_buffer == nullptr) {
|
||||
if (data_addr == nullptr || out_buffer_descriptor == nullptr) {
|
||||
LOGE("[OEMCrypto_CopyBuffer(): OEMCrypto_ERROR_INVALID_CONTEXT]");
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
@@ -649,13 +591,13 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_CopyBuffer(
|
||||
LOGE("[OEMCrypto_CopyBuffer(): OEMCrypto_ERROR_BUFFER_TOO_LARGE]");
|
||||
return OEMCrypto_ERROR_BUFFER_TOO_LARGE;
|
||||
}
|
||||
OEMCryptoResult status =
|
||||
crypto_engine->SetDestination(out_buffer, data_length, subsample_flags);
|
||||
OEMCryptoResult status = crypto_engine->SetDestination(
|
||||
out_buffer_descriptor, data_length, subsample_flags);
|
||||
if (status != OEMCrypto_SUCCESS) return status;
|
||||
if (crypto_engine->destination() != nullptr) {
|
||||
memmove(crypto_engine->destination(), data_addr, data_length);
|
||||
}
|
||||
return crypto_engine->PushDestination(out_buffer, subsample_flags);
|
||||
return crypto_engine->PushDestination(out_buffer_descriptor, subsample_flags);
|
||||
}
|
||||
|
||||
OEMCRYPTO_API OEMCryptoResult OEMCrypto_WrapKeyboxOrOEMCert(
|
||||
@@ -750,7 +692,7 @@ OEMCrypto_LoadOEMPrivateKey(OEMCrypto_SESSION session) {
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
if (crypto_engine->config_provisioning_method() != OEMCrypto_OEMCertificate) {
|
||||
LOGE("Provisioning method = %d.",
|
||||
LOGE("Unexpected provisioning method = %d.",
|
||||
crypto_engine->config_provisioning_method());
|
||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
@@ -769,7 +711,7 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_GetOEMPublicCertificate(
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
if (crypto_engine->config_provisioning_method() != OEMCrypto_OEMCertificate) {
|
||||
LOGE("Provisioning method = %d.",
|
||||
LOGE("Unexpected provisioning method = %d.",
|
||||
crypto_engine->config_provisioning_method());
|
||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
@@ -782,11 +724,6 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_GetDeviceID(uint8_t* deviceID,
|
||||
LOGE("OEMCrypto_GetDeviceID: OEMCrypto Not Initialized.");
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
if (crypto_engine->config_provisioning_method() != OEMCrypto_Keybox) {
|
||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
// Devices that do not support a keybox should use some other method to
|
||||
// store the device id.
|
||||
const std::vector<uint8_t>& dev_id_string = crypto_engine->DeviceRootId();
|
||||
if (dev_id_string.empty()) {
|
||||
LOGE("[OEMCrypto_GetDeviceId(): Keybox Invalid]");
|
||||
@@ -849,7 +786,8 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_GetRandom(uint8_t* randomData,
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
|
||||
OEMCRYPTO_API OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey30(
|
||||
// This function is no longer exported -- it is only used by LoadProvisioning.
|
||||
OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey30(
|
||||
OEMCrypto_SESSION session, const uint32_t* unaligned_nonce,
|
||||
const uint8_t* encrypted_message_key, size_t encrypted_message_key_length,
|
||||
const uint8_t* enc_rsa_key, size_t enc_rsa_key_length,
|
||||
@@ -871,7 +809,7 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey30(
|
||||
// key are the same size -- just encrypted with different keys.
|
||||
// We add 32 bytes for a context, 32 for iv, and 32 bytes for a signature.
|
||||
// Important: This layout must match OEMCrypto_LoadDeviceRSAKey below.
|
||||
size_t buffer_size = enc_rsa_key_length + sizeof(WrappedRSAKey);
|
||||
const size_t buffer_size = enc_rsa_key_length + sizeof(WrappedRSAKey);
|
||||
|
||||
if (wrapped_rsa_key == nullptr || *wrapped_rsa_key_length < buffer_size) {
|
||||
*wrapped_rsa_key_length = buffer_size;
|
||||
@@ -915,11 +853,8 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey30(
|
||||
}
|
||||
size_t padding = pkcs8_rsa_key[enc_rsa_key_length - 1];
|
||||
if (padding > 16) {
|
||||
LOGE(
|
||||
"[OEMCrypto_RewrapDeviceRSAKey30(): "
|
||||
"Encrypted RSA has bad padding: %d]",
|
||||
padding);
|
||||
return OEMCrypto_ERROR_INVALID_RSA_KEY;
|
||||
// Do not return an error at this point, to avoid a padding oracle attack.
|
||||
padding = 0;
|
||||
}
|
||||
size_t rsa_key_length = enc_rsa_key_length - padding;
|
||||
if (!session_ctx->LoadRSAKey(&pkcs8_rsa_key[0], rsa_key_length)) {
|
||||
@@ -965,7 +900,8 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey30(
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
|
||||
OEMCRYPTO_API OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey(
|
||||
// This function is no longer exported -- it is only used by LoadProvisioning.
|
||||
OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey(
|
||||
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
|
||||
const uint8_t* signature, size_t signature_length,
|
||||
const uint32_t* unaligned_nonce, const uint8_t* enc_rsa_key,
|
||||
@@ -990,7 +926,7 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey(
|
||||
// key are the same size -- just encrypted with different keys.
|
||||
// We add 32 bytes for a context, 32 for iv, and 32 bytes for a signature.
|
||||
// Important: This layout must match OEMCrypto_LoadDeviceRSAKey below.
|
||||
size_t buffer_size = enc_rsa_key_length + sizeof(WrappedRSAKey);
|
||||
const size_t buffer_size = enc_rsa_key_length + sizeof(WrappedRSAKey);
|
||||
|
||||
if (wrapped_rsa_key == nullptr || *wrapped_rsa_key_length < buffer_size) {
|
||||
*wrapped_rsa_key_length = buffer_size;
|
||||
@@ -1013,40 +949,33 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey(
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
|
||||
// Range check
|
||||
if (!RangeCheck(message, message_length,
|
||||
reinterpret_cast<const uint8_t*>(unaligned_nonce),
|
||||
sizeof(uint32_t), true) ||
|
||||
!RangeCheck(message, message_length, enc_rsa_key, enc_rsa_key_length,
|
||||
true) ||
|
||||
!RangeCheck(message, message_length, enc_rsa_key_iv, wvoec::KEY_IV_SIZE, true)) {
|
||||
LOGE("[OEMCrypto_RewrapDeviceRSAKey(): - range check.]");
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
// verify signature.
|
||||
if (!session_ctx->ValidateMessage(message, message_length, signature,
|
||||
signature_length)) {
|
||||
LOGE("[RewrapDeviceRSAKey(): Could not verify signature]");
|
||||
return OEMCrypto_ERROR_SIGNATURE_FAILURE;
|
||||
}
|
||||
|
||||
// Range check performed by ODK library.
|
||||
|
||||
// Validate nonce
|
||||
if (!session_ctx->CheckNonce(nonce)) {
|
||||
return OEMCrypto_ERROR_INVALID_NONCE;
|
||||
}
|
||||
|
||||
// Decrypt RSA key.
|
||||
// Decrypt RSA key and verify it.
|
||||
std::vector<uint8_t> pkcs8_rsa_key(enc_rsa_key_length);
|
||||
if (!session_ctx->DecryptRSAKey(enc_rsa_key, enc_rsa_key_length,
|
||||
enc_rsa_key_iv, &pkcs8_rsa_key[0])) {
|
||||
return OEMCrypto_ERROR_INVALID_RSA_KEY;
|
||||
}
|
||||
|
||||
size_t padding = pkcs8_rsa_key[enc_rsa_key_length - 1];
|
||||
if (padding > 16) {
|
||||
LOGE("[RewrapDeviceRSAKey(): Encrypted RSA has bad padding: %d]", padding);
|
||||
return OEMCrypto_ERROR_INVALID_RSA_KEY;
|
||||
// Do not return an error at this point, to avoid a padding oracle attack.
|
||||
padding = 0;
|
||||
}
|
||||
size_t rsa_key_length = enc_rsa_key_length - padding;
|
||||
// verify signature, verify RSA key, and load it.
|
||||
if (!session_ctx->ValidateMessage(message, message_length, signature,
|
||||
signature_length)) {
|
||||
LOGE("[RewrapDeviceRSAKey(): Could not verify signature]");
|
||||
return OEMCrypto_ERROR_SIGNATURE_FAILURE;
|
||||
}
|
||||
if (!session_ctx->LoadRSAKey(&pkcs8_rsa_key[0], rsa_key_length)) {
|
||||
return OEMCrypto_ERROR_INVALID_RSA_KEY;
|
||||
}
|
||||
@@ -1085,6 +1014,84 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey(
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
|
||||
OEMCRYPTO_API OEMCryptoResult OEMCrypto_LoadProvisioning(
|
||||
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
|
||||
size_t core_message_length, const uint8_t* signature,
|
||||
size_t signature_length, uint8_t* wrapped_private_key,
|
||||
size_t* wrapped_private_key_length) {
|
||||
if (crypto_engine == nullptr) {
|
||||
LOGE("OEMCrypto Not Initialized");
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
if (wrapped_private_key_length == nullptr) {
|
||||
LOGE("OEMCrypto_ERROR_INVALID_CONTEXT");
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
if (message == nullptr || message_length == 0 || signature == nullptr ||
|
||||
signature_length == 0) {
|
||||
LOGE("OEMCrypto_ERROR_INVALID_CONTEXT");
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
if (!crypto_engine->ValidRootOfTrust()) {
|
||||
LOGE("ERROR_KEYBOX_INVALID");
|
||||
return OEMCrypto_ERROR_KEYBOX_INVALID;
|
||||
}
|
||||
SessionContext* session_ctx = crypto_engine->FindSession(session);
|
||||
if (session_ctx == nullptr || !session_ctx->isValid()) {
|
||||
LOGE("ERROR_INVALID_SESSION");
|
||||
return OEMCrypto_ERROR_INVALID_SESSION;
|
||||
}
|
||||
std::vector<uint8_t> device_id = crypto_engine->DeviceRootId();
|
||||
ODK_ParsedProvisioning parsed_response;
|
||||
const uint32_t nonce = session_ctx->nonce();
|
||||
const OEMCryptoResult result =
|
||||
ODK_ParseProvisioning(message, message_length, core_message_length,
|
||||
&(session_ctx->nonce_values()), device_id.data(),
|
||||
device_id.size(), &parsed_response);
|
||||
if (result != OEMCrypto_SUCCESS) {
|
||||
LOGE("ODK Error %d", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
// For the reference implementation, the wrapped key and the encrypted
|
||||
// key are the same size -- just encrypted with different keys.
|
||||
// We add 32 bytes for a context, 32 for iv, and 32 bytes for a signature.
|
||||
// Important: This layout must match OEMCrypto_LoadDeviceRSAKey below.
|
||||
const size_t buffer_size =
|
||||
parsed_response.enc_private_key.length + sizeof(WrappedRSAKey);
|
||||
|
||||
if (wrapped_private_key == nullptr ||
|
||||
*wrapped_private_key_length < buffer_size) {
|
||||
*wrapped_private_key_length = buffer_size;
|
||||
return OEMCrypto_ERROR_SHORT_BUFFER;
|
||||
}
|
||||
*wrapped_private_key_length =
|
||||
buffer_size; // Tell caller how much space we used.
|
||||
const uint8_t* message_body = message + core_message_length;
|
||||
if (crypto_engine->config_provisioning_method() == OEMCrypto_Keybox) {
|
||||
return OEMCrypto_RewrapDeviceRSAKey(
|
||||
session, message, message_length, signature, signature_length, &nonce,
|
||||
message_body + parsed_response.enc_private_key.offset,
|
||||
parsed_response.enc_private_key.length,
|
||||
message_body + parsed_response.enc_private_key_iv.offset,
|
||||
wrapped_private_key, wrapped_private_key_length);
|
||||
} else if (crypto_engine->config_provisioning_method() ==
|
||||
OEMCrypto_OEMCertificate) {
|
||||
return OEMCrypto_RewrapDeviceRSAKey30(
|
||||
session, &nonce,
|
||||
message_body + parsed_response.encrypted_message_key.offset,
|
||||
parsed_response.encrypted_message_key.length,
|
||||
message_body + parsed_response.enc_private_key.offset,
|
||||
parsed_response.enc_private_key.length,
|
||||
message_body + parsed_response.enc_private_key_iv.offset,
|
||||
wrapped_private_key, wrapped_private_key_length);
|
||||
} else {
|
||||
LOGE("Invalid provisioning method: %d.",
|
||||
crypto_engine->config_provisioning_method());
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
OEMCRYPTO_API OEMCryptoResult OEMCrypto_LoadDeviceRSAKey(
|
||||
OEMCrypto_SESSION session, const uint8_t* wrapped_rsa_key,
|
||||
size_t wrapped_rsa_key_length) {
|
||||
@@ -1127,6 +1134,13 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_LoadDeviceRSAKey(
|
||||
context)) {
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
// verify signature.
|
||||
if (!session_ctx->ValidateMessage(
|
||||
wrapped->context, wrapped_rsa_key_length - sizeof(wrapped->signature),
|
||||
wrapped->signature, sizeof(wrapped->signature))) {
|
||||
LOGE("[LoadDeviceRSAKey(): Could not verify signature]");
|
||||
return OEMCrypto_ERROR_SIGNATURE_FAILURE;
|
||||
}
|
||||
// Decrypt RSA key.
|
||||
std::vector<uint8_t> pkcs8_rsa_key(wrapped_rsa_key_length -
|
||||
sizeof(wrapped->signature));
|
||||
@@ -1137,17 +1151,10 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_LoadDeviceRSAKey(
|
||||
}
|
||||
size_t padding = pkcs8_rsa_key[enc_rsa_key_length - 1];
|
||||
if (padding > 16) {
|
||||
LOGE("[LoadDeviceRSAKey(): Encrypted RSA has bad padding: %d]", padding);
|
||||
return OEMCrypto_ERROR_INVALID_RSA_KEY;
|
||||
// Do not return an error at this point, to avoid a padding oracle attack.
|
||||
padding = 0;
|
||||
}
|
||||
size_t rsa_key_length = enc_rsa_key_length - padding;
|
||||
// verify signature.
|
||||
if (!session_ctx->ValidateMessage(
|
||||
wrapped->context, wrapped_rsa_key_length - sizeof(wrapped->signature),
|
||||
wrapped->signature, sizeof(wrapped->signature))) {
|
||||
LOGE("[LoadDeviceRSAKey(): Could not verify signature]");
|
||||
return OEMCrypto_ERROR_SIGNATURE_FAILURE;
|
||||
}
|
||||
if (!session_ctx->LoadRSAKey(&pkcs8_rsa_key[0], rsa_key_length)) {
|
||||
return OEMCrypto_ERROR_INVALID_RSA_KEY;
|
||||
}
|
||||
@@ -1239,7 +1246,9 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_DeriveKeysFromSessionKey(
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
|
||||
OEMCRYPTO_API uint32_t OEMCrypto_APIVersion() { return 16; }
|
||||
OEMCRYPTO_API uint32_t OEMCrypto_APIVersion() {
|
||||
return CryptoEngine::kApiVersion;
|
||||
}
|
||||
|
||||
OEMCRYPTO_API uint8_t OEMCrypto_Security_Patch_Level() {
|
||||
uint8_t security_patch_level = crypto_engine->config_security_patch_level();
|
||||
|
||||
@@ -157,6 +157,32 @@ EntitlementKey* EntitlementKeysContext::GetEntitlementKey(
|
||||
|
||||
/***************************************/
|
||||
|
||||
SessionContext::SessionContext(CryptoEngine* ce, SessionId sid,
|
||||
const RSA_shared_ptr& rsa_key)
|
||||
: valid_(true),
|
||||
ce_(ce),
|
||||
id_(sid),
|
||||
current_content_key_(nullptr),
|
||||
session_keys_(nullptr),
|
||||
rsa_key_(rsa_key),
|
||||
allowed_schemes_(kSign_RSASSA_PSS),
|
||||
timer_limits_(),
|
||||
clock_values_(),
|
||||
usage_entry_(nullptr),
|
||||
srm_requirements_status_(NoSRMVersion),
|
||||
usage_entry_status_(kNoUsageEntry),
|
||||
compute_hash_(false),
|
||||
current_hash_(0),
|
||||
bad_frame_number_(0),
|
||||
hash_error_(OEMCrypto_SUCCESS),
|
||||
state_nonce_created_(false),
|
||||
state_request_signed_(false),
|
||||
state_response_loaded_(false) {
|
||||
nonce_values_.api_version = 16;
|
||||
nonce_values_.nonce = 0;
|
||||
nonce_values_.session_id = sid;
|
||||
}
|
||||
|
||||
SessionContext::~SessionContext() {
|
||||
if (usage_entry_) {
|
||||
delete usage_entry_;
|
||||
@@ -287,130 +313,257 @@ bool SessionContext::RSADeriveKeys(
|
||||
return DeriveKeys(session_key_, mac_key_context, enc_key_context);
|
||||
}
|
||||
|
||||
OEMCryptoResult SessionContext::PrepAndSignLicenseRequest(
|
||||
uint8_t* message, size_t message_length, size_t* core_message_length,
|
||||
uint8_t* signature, size_t* signature_length) {
|
||||
if (signature_length == nullptr || core_message_length == nullptr) {
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
const size_t required_signature_size = CertSignatureSize();
|
||||
OEMCryptoResult result = ODK_PrepareCoreLicenseRequest(
|
||||
message, message_length, core_message_length, &nonce_values_);
|
||||
if (*signature_length < required_signature_size ||
|
||||
result == OEMCrypto_ERROR_SHORT_BUFFER) {
|
||||
*signature_length = required_signature_size;
|
||||
return OEMCrypto_ERROR_SHORT_BUFFER;
|
||||
}
|
||||
if (result != OEMCrypto_SUCCESS) {
|
||||
LOGE("ODK error: %d", result);
|
||||
return result;
|
||||
}
|
||||
if (message == nullptr || message_length == 0 || signature == nullptr) {
|
||||
LOGE("OEMCrypto_ERROR_INVALID_CONTEXT");
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
if (state_request_signed_) {
|
||||
LOGE("Attempt to sign two license requests");
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
result = GenerateCertSignature(message, message_length, signature,
|
||||
signature_length);
|
||||
if (result == OEMCrypto_SUCCESS) state_request_signed_ = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
OEMCryptoResult SessionContext::PrepAndSignRenewalRequest(
|
||||
uint8_t* message, size_t message_length, size_t* core_message_length,
|
||||
uint8_t* signature, size_t* signature_length) {
|
||||
if (signature_length == nullptr || core_message_length == nullptr) {
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
// If we have signed a request, but have not loaded it, something is wrong.
|
||||
// On the other hand, we can sign a license release using the mac keys from
|
||||
// the usage table.
|
||||
if (state_request_signed_ && !state_response_loaded_) {
|
||||
LOGE("Attempt to sign renewal before load");
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
const size_t required_signature_size = SHA256_DIGEST_LENGTH;
|
||||
const uint64_t now = CurrentTimer();
|
||||
const OEMCryptoResult result = ODK_PrepareCoreRenewalRequest(
|
||||
message, message_length, core_message_length, &nonce_values_,
|
||||
&clock_values_, now);
|
||||
if (*signature_length < required_signature_size ||
|
||||
result == OEMCrypto_ERROR_SHORT_BUFFER) {
|
||||
*signature_length = required_signature_size;
|
||||
return OEMCrypto_ERROR_SHORT_BUFFER;
|
||||
}
|
||||
if (result != OEMCrypto_SUCCESS) {
|
||||
LOGE("ODK error: %d", result);
|
||||
return result;
|
||||
}
|
||||
if (message == nullptr || message_length == 0 || signature == nullptr) {
|
||||
LOGE("OEMCrypto_ERROR_INVALID_CONTEXT");
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
return GenerateSignature(message, message_length, signature,
|
||||
signature_length);
|
||||
}
|
||||
|
||||
OEMCryptoResult SessionContext::PrepAndSignProvisioningRequest(
|
||||
uint8_t* message, size_t message_length, size_t* core_message_length,
|
||||
uint8_t* signature, size_t* signature_length) {
|
||||
if (signature_length == nullptr || core_message_length == nullptr) {
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
if (state_request_signed_) {
|
||||
LOGE("Attempt to sign prov request after license request");
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
const size_t required_signature_size = ROTSignatureSize();
|
||||
if (required_signature_size == 0) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
const std::vector<uint8_t>& device_id = ce_->DeviceRootId();
|
||||
OEMCryptoResult result = ODK_PrepareCoreProvisioningRequest(
|
||||
message, message_length, core_message_length, &nonce_values_,
|
||||
device_id.data(), device_id.size());
|
||||
if (*signature_length < required_signature_size ||
|
||||
result == OEMCrypto_ERROR_SHORT_BUFFER) {
|
||||
*signature_length = required_signature_size;
|
||||
return OEMCrypto_ERROR_SHORT_BUFFER;
|
||||
}
|
||||
if (result != OEMCrypto_SUCCESS) {
|
||||
LOGE("ODK error: %d", result);
|
||||
return result;
|
||||
}
|
||||
if (message == nullptr || message_length == 0 || signature == nullptr) {
|
||||
LOGE("OEMCrypto_ERROR_INVALID_CONTEXT");
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
if (ce_->config_provisioning_method() == OEMCrypto_Keybox) {
|
||||
result =
|
||||
GenerateSignature(message, message_length, signature, signature_length);
|
||||
} else if (ce_->config_provisioning_method() == OEMCrypto_OEMCertificate) {
|
||||
result = GenerateCertSignature(message, message_length, signature,
|
||||
signature_length);
|
||||
} else {
|
||||
LOGE("Bad prov method = %d", ce_->config_provisioning_method());
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
if (result == OEMCrypto_SUCCESS) state_request_signed_ = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Utility function to generate a message signature
|
||||
bool SessionContext::GenerateSignature(const uint8_t* message,
|
||||
size_t message_length,
|
||||
uint8_t* signature,
|
||||
size_t* signature_length,
|
||||
bool renewal_message) {
|
||||
OEMCryptoResult SessionContext::GenerateSignature(const uint8_t* message,
|
||||
size_t message_length,
|
||||
uint8_t* signature,
|
||||
size_t* signature_length) {
|
||||
if (message == nullptr || message_length == 0 || signature == nullptr ||
|
||||
signature_length == nullptr) {
|
||||
LOGE("[OEMCrypto_GenerateSignature(): OEMCrypto_ERROR_INVALID_CONTEXT]");
|
||||
return false;
|
||||
LOGE("OEMCrypto_ERROR_INVALID_CONTEXT");
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
|
||||
if (mac_key_client_.size() != wvoec::MAC_KEY_SIZE) {
|
||||
return false;
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
|
||||
if (*signature_length != SHA256_DIGEST_LENGTH) {
|
||||
*signature_length = SHA256_DIGEST_LENGTH;
|
||||
return false;
|
||||
return OEMCrypto_ERROR_SHORT_BUFFER;
|
||||
}
|
||||
|
||||
if (!renewal_message) {
|
||||
if (state_request_signed_) {
|
||||
return false;
|
||||
}
|
||||
state_request_signed_ = true;
|
||||
}
|
||||
|
||||
unsigned int md_len = *signature_length;
|
||||
if (HMAC(EVP_sha256(), &mac_key_client_[0], wvoec::MAC_KEY_SIZE, message,
|
||||
message_length, signature, &md_len)) {
|
||||
*signature_length = md_len;
|
||||
return true;
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
return false;
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
|
||||
// This is ussd when the device is a cast receiver.
|
||||
size_t SessionContext::RSASignatureSize() {
|
||||
if (!rsa_key()) {
|
||||
LOGE("[GenerateRSASignature(): no RSA key set]");
|
||||
LOGE("no RSA key set");
|
||||
return 0;
|
||||
}
|
||||
return static_cast<size_t>(RSA_size(rsa_key()));
|
||||
}
|
||||
|
||||
size_t SessionContext::CertSignatureSize() {
|
||||
// TODO(b/67735947): Add ECC cert support.
|
||||
if (!rsa_key()) {
|
||||
LOGE("No private key set");
|
||||
return 0;
|
||||
}
|
||||
return static_cast<size_t>(RSA_size(rsa_key()));
|
||||
}
|
||||
|
||||
size_t SessionContext::ROTSignatureSize() {
|
||||
if (ce_->config_provisioning_method() == OEMCrypto_Keybox)
|
||||
return SHA256_DIGEST_LENGTH;
|
||||
if (ce_->config_provisioning_method() == OEMCrypto_OEMCertificate)
|
||||
return CertSignatureSize();
|
||||
LOGE("Bad prov method = %d", ce_->config_provisioning_method());
|
||||
return 0;
|
||||
}
|
||||
|
||||
OEMCryptoResult SessionContext::GenerateCertSignature(
|
||||
const uint8_t* message, size_t message_length, uint8_t* signature,
|
||||
size_t* signature_length) {
|
||||
// TODO(b/67735947): Add ECC cert support.
|
||||
if (message == nullptr || message_length == 0 || signature == nullptr ||
|
||||
signature_length == 0) {
|
||||
LOGE("OEMCrypto_ERROR_INVALID_CONTEXT");
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
if (!rsa_key()) {
|
||||
LOGE("no RSA key set");
|
||||
return OEMCrypto_ERROR_INVALID_RSA_KEY;
|
||||
}
|
||||
if (*signature_length < static_cast<size_t>(RSA_size(rsa_key()))) {
|
||||
*signature_length = CertSignatureSize();
|
||||
return OEMCrypto_ERROR_SHORT_BUFFER;
|
||||
}
|
||||
if (allowed_schemes_ != kSign_RSASSA_PSS) {
|
||||
LOGE("message signing not allowed");
|
||||
return OEMCrypto_ERROR_INVALID_RSA_KEY;
|
||||
}
|
||||
|
||||
// Hash the message using SHA1.
|
||||
uint8_t hash[SHA_DIGEST_LENGTH];
|
||||
if (!SHA1(message, message_length, hash)) {
|
||||
LOGE("Error creating signature hash");
|
||||
dump_boringssl_error();
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
|
||||
// Add PSS padding.
|
||||
std::vector<uint8_t> padded_digest(*signature_length);
|
||||
int status = RSA_padding_add_PKCS1_PSS_mgf1(
|
||||
rsa_key(), &padded_digest[0], hash, EVP_sha1(), nullptr, kPssSaltLength);
|
||||
if (status == -1) {
|
||||
LOGE("Error padding hash");
|
||||
dump_boringssl_error();
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
|
||||
// Encrypt PSS padded digest.
|
||||
status = RSA_private_encrypt(*signature_length, &padded_digest[0], signature,
|
||||
rsa_key(), RSA_NO_PADDING);
|
||||
if (status == -1) {
|
||||
LOGE("Error in private encrypt");
|
||||
dump_boringssl_error();
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
|
||||
OEMCryptoResult SessionContext::GenerateRSASignature(
|
||||
const uint8_t* message, size_t message_length, uint8_t* signature,
|
||||
size_t* signature_length, RSA_Padding_Scheme padding_scheme) {
|
||||
if (message == nullptr || message_length == 0 || signature == nullptr ||
|
||||
signature_length == 0) {
|
||||
LOGE("[GenerateRSASignature(): OEMCrypto_ERROR_INVALID_CONTEXT]");
|
||||
LOGE("OEMCrypto_ERROR_INVALID_CONTEXT");
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
if (!rsa_key()) {
|
||||
LOGE("[GenerateRSASignature(): no RSA key set]");
|
||||
LOGE("no RSA key set");
|
||||
return OEMCrypto_ERROR_INVALID_RSA_KEY;
|
||||
}
|
||||
if (*signature_length < static_cast<size_t>(RSA_size(rsa_key()))) {
|
||||
*signature_length = RSA_size(rsa_key());
|
||||
return OEMCrypto_ERROR_SHORT_BUFFER;
|
||||
}
|
||||
if ((padding_scheme & allowed_schemes_) != padding_scheme) {
|
||||
LOGE("[GenerateRSASignature(): padding_scheme not allowed]");
|
||||
if (((padding_scheme & allowed_schemes_) != padding_scheme) ||
|
||||
(padding_scheme != kSign_PKCS1_Block1)) {
|
||||
LOGE("padding_scheme not allowed");
|
||||
return OEMCrypto_ERROR_INVALID_RSA_KEY;
|
||||
}
|
||||
// This is the standard padding scheme used for license requests.
|
||||
// TODO(b/135288022): This first padding scheme will be used only for signing
|
||||
// messages, as in OEMCrypto_Sign*Request.
|
||||
if (padding_scheme == kSign_RSASSA_PSS) {
|
||||
if (state_request_signed_) {
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
state_request_signed_ = true;
|
||||
|
||||
// Hash the message using SHA1.
|
||||
uint8_t hash[SHA_DIGEST_LENGTH];
|
||||
if (!SHA1(message, message_length, hash)) {
|
||||
LOGE("[GenerateRSASignature(): error creating signature hash.]");
|
||||
dump_boringssl_error();
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
|
||||
// Add PSS padding.
|
||||
std::vector<uint8_t> padded_digest(*signature_length);
|
||||
int status =
|
||||
RSA_padding_add_PKCS1_PSS_mgf1(rsa_key(), &padded_digest[0], hash,
|
||||
EVP_sha1(), nullptr, kPssSaltLength);
|
||||
if (status == -1) {
|
||||
LOGE("[GenerateRSASignature(): error padding hash.]");
|
||||
dump_boringssl_error();
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
|
||||
// Encrypt PSS padded digest.
|
||||
status = RSA_private_encrypt(*signature_length, &padded_digest[0],
|
||||
signature, rsa_key(), RSA_NO_PADDING);
|
||||
if (status == -1) {
|
||||
LOGE("[GenerateRSASignature(): error in private encrypt.]");
|
||||
dump_boringssl_error();
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
// This is the alternate padding scheme used by cast receivers only.
|
||||
} else if (padding_scheme == kSign_PKCS1_Block1) {
|
||||
// TODO(b/135288022): Alternate padding scheme is not used for messages, so
|
||||
// we do not need to keep track of the state, like we do above. This
|
||||
// padding scheme will be left over as the only valid option for this
|
||||
// function. The padding scheme above will be used for signing messages.
|
||||
|
||||
if (message_length > 83) {
|
||||
LOGE("[GenerateRSASignature(): RSA digest too large.]");
|
||||
return OEMCrypto_ERROR_SIGNATURE_FAILURE;
|
||||
}
|
||||
// Pad the message with PKCS1 padding, and then encrypt.
|
||||
size_t status = RSA_private_encrypt(message_length, message, signature,
|
||||
rsa_key(), RSA_PKCS1_PADDING);
|
||||
if (status != *signature_length) {
|
||||
LOGE("[GenerateRSASignature(): error in RSA private encrypt. status=%d]",
|
||||
status);
|
||||
dump_boringssl_error();
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
} else { // Bad RSA_Padding_Scheme
|
||||
return OEMCrypto_ERROR_INVALID_RSA_KEY;
|
||||
// This is the maximum digest size possible for PKCS1 block type 1,
|
||||
// as used for a CAST receiver.
|
||||
const size_t max_digest_size = 83u;
|
||||
if (message_length > max_digest_size) {
|
||||
LOGE("RSA digest too large");
|
||||
return OEMCrypto_ERROR_SIGNATURE_FAILURE;
|
||||
}
|
||||
// Pad the message with PKCS1 padding, and then encrypt.
|
||||
int status = RSA_private_encrypt(message_length, message, signature,
|
||||
rsa_key(), RSA_PKCS1_PADDING);
|
||||
if (status < 0) {
|
||||
LOGE("error in RSA private encrypt. status=%d", status);
|
||||
dump_boringssl_error();
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
*signature_length = static_cast<size_t>(RSA_size(rsa_key()));
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -427,7 +580,7 @@ bool SessionContext::ValidateMessage(const uint8_t* given_message,
|
||||
unsigned int md_len = SHA256_DIGEST_LENGTH;
|
||||
if (!HMAC(EVP_sha256(), mac_key_server_.data(), mac_key_server_.size(),
|
||||
given_message, message_length, computed_signature, &md_len)) {
|
||||
LOGE("ValidateMessage: Could not compute signature.");
|
||||
LOGE("ValidateMessage: Could not compute signature");
|
||||
return false;
|
||||
}
|
||||
if (memcmp(given_signature, computed_signature, signature_length)) {
|
||||
@@ -443,16 +596,16 @@ bool SessionContext::ValidateMessage(const uint8_t* given_message,
|
||||
OEMCryptoResult SessionContext::CheckStatusOnline(uint32_t nonce,
|
||||
uint32_t control) {
|
||||
if (!(control & wvoec::kControlNonceEnabled)) {
|
||||
LOGE("LoadKeys: Server provided Nonce_Required but Nonce_Enabled = 0.");
|
||||
LOGE("LoadKeys: Server provided Nonce_Required but Nonce_Enabled = 0");
|
||||
// Server error. Continue, and assume nonce required.
|
||||
}
|
||||
if (!CheckNonce(nonce)) return OEMCrypto_ERROR_INVALID_NONCE;
|
||||
switch (usage_entry_status_) {
|
||||
case kNoUsageEntry:
|
||||
LOGE("LoadKeys: Session did not create usage entry.");
|
||||
LOGE("LoadKeys: Session did not create usage entry");
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
case kUsageEntryLoaded:
|
||||
LOGE("LoadKeys: Session reloaded existing entry.");
|
||||
LOGE("LoadKeys: Session reloaded existing entry");
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
case kUsageEntryNew:
|
||||
return OEMCrypto_SUCCESS;
|
||||
@@ -464,12 +617,12 @@ OEMCryptoResult SessionContext::CheckStatusOnline(uint32_t nonce,
|
||||
OEMCryptoResult SessionContext::CheckStatusOffline(uint32_t nonce,
|
||||
uint32_t control) {
|
||||
if (control & wvoec::kControlNonceEnabled) {
|
||||
LOGE("KCB: Server provided NonceOrEntry but Nonce_Enabled = 1.");
|
||||
LOGE("KCB: Server provided NonceOrEntry but Nonce_Enabled = 1");
|
||||
// Server error. Continue, and assume nonce required.
|
||||
}
|
||||
switch (usage_entry_status_) {
|
||||
case kNoUsageEntry:
|
||||
LOGE("LoadKeys: Session did not create or load usage entry.");
|
||||
LOGE("LoadKeys: Session did not create or load usage entry");
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
case kUsageEntryLoaded:
|
||||
// Repeat load. Calling function will verify pst and keys.
|
||||
@@ -507,8 +660,8 @@ OEMCryptoResult SessionContext::CheckNonceOrEntry(
|
||||
void SessionContext::StartTimer() { timer_start_ = ce_->OnlineTime(); }
|
||||
|
||||
uint32_t SessionContext::CurrentTimer() {
|
||||
time_t now = ce_->OnlineTime();
|
||||
return now - timer_start_;
|
||||
int64_t now = ce_->OnlineTime();
|
||||
return static_cast<uint32_t>((now >= timer_start_) ? now - timer_start_ : 0);
|
||||
}
|
||||
|
||||
OEMCryptoResult SessionContext::LoadKeys(
|
||||
@@ -523,7 +676,7 @@ OEMCryptoResult SessionContext::LoadKeys(
|
||||
return OEMCrypto_ERROR_SIGNATURE_FAILURE;
|
||||
}
|
||||
if (state_response_loaded_) {
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
return OEMCrypto_ERROR_LICENSE_RELOAD;
|
||||
}
|
||||
state_response_loaded_ = true;
|
||||
|
||||
@@ -560,7 +713,7 @@ OEMCryptoResult SessionContext::LoadKeys(
|
||||
message + srm_restriction_data.offset + 8));
|
||||
uint16_t current_version = 0;
|
||||
if (OEMCrypto_SUCCESS != ce_->current_srm_version(¤t_version)) {
|
||||
LOGW("[LoadKeys: SRM Version not available.");
|
||||
LOGW("[LoadKeys: SRM Version not available");
|
||||
srm_requirements_status_ = InvalidSRMVersion;
|
||||
} else if (current_version < minimum_version) {
|
||||
LOGW("[LoadKeys: SRM Version is too small %d, required: %d",
|
||||
@@ -635,7 +788,7 @@ OEMCryptoResult SessionContext::LoadKeys(
|
||||
switch (usage_entry_status_) {
|
||||
case kNoUsageEntry:
|
||||
if (pst.length > 0) {
|
||||
LOGE("LoadKeys: PST specified but no usage entry loaded.");
|
||||
LOGE("LoadKeys: PST specified but no usage entry loaded");
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
break; // no extra check.
|
||||
@@ -666,9 +819,8 @@ OEMCryptoResult SessionContext::LoadKeys(
|
||||
}
|
||||
|
||||
OEMCryptoResult SessionContext::LoadEntitledContentKeys(
|
||||
const uint8_t* message, size_t message_length,
|
||||
const OEMCrypto_EntitledContentKeyObject* key_array,
|
||||
size_t key_array_length) {
|
||||
const uint8_t* message, size_t message_length, size_t key_array_length,
|
||||
const OEMCrypto_EntitledContentKeyObject* key_array) {
|
||||
if (!key_array) {
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
@@ -745,18 +897,18 @@ OEMCryptoResult SessionContext::InstallKey(
|
||||
|
||||
KeyControlBlock key_control_block(key_control_str);
|
||||
if (!key_control_block.valid()) {
|
||||
LOGE("Error parsing key control.");
|
||||
LOGE("Error parsing key control");
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
if ((key_control_block.control_bits() &
|
||||
wvoec::kControlRequireAntiRollbackHardware) &&
|
||||
!ce_->config_is_anti_rollback_hw_present()) {
|
||||
LOGE("Anti-rollback hardware is required but hardware not present.");
|
||||
LOGE("Anti-rollback hardware is required but hardware not present");
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
uint8_t minimum_patch_level =
|
||||
(key_control_block.control_bits() & wvoec::kControlSecurityPatchLevelMask) >>
|
||||
wvoec::kControlSecurityPatchLevelShift;
|
||||
uint8_t minimum_patch_level = (key_control_block.control_bits() &
|
||||
wvoec::kControlSecurityPatchLevelMask) >>
|
||||
wvoec::kControlSecurityPatchLevelShift;
|
||||
if (minimum_patch_level > OEMCrypto_Security_Patch_Level()) {
|
||||
LOGE("[InstallKey(): security patch level: %d. Minimum:%d]",
|
||||
OEMCrypto_Security_Patch_Level(), minimum_patch_level);
|
||||
@@ -764,7 +916,7 @@ OEMCryptoResult SessionContext::InstallKey(
|
||||
}
|
||||
OEMCryptoResult result = CheckNonceOrEntry(key_control_block);
|
||||
if (result != OEMCrypto_SUCCESS) {
|
||||
LOGE("LoadKeys: Failed Nonce/PST check.");
|
||||
LOGE("LoadKeys: Failed Nonce/PST check");
|
||||
return result;
|
||||
}
|
||||
if (key_control_block.control_bits() & wvoec::kControlSRMVersionRequired) {
|
||||
@@ -818,7 +970,7 @@ OEMCryptoResult SessionContext::RefreshKey(
|
||||
// Key control is not encrypted if key id is nullptr
|
||||
KeyControlBlock key_control_block(key_control);
|
||||
if (!key_control_block.valid()) {
|
||||
LOGE("Parse key control error.");
|
||||
LOGE("Parse key control error");
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
if ((key_control_block.control_bits() & wvoec::kControlNonceEnabled) &&
|
||||
@@ -834,7 +986,7 @@ OEMCryptoResult SessionContext::RefreshKey(
|
||||
Key* content_key = session_keys_->Find(key_id);
|
||||
|
||||
if (content_key == nullptr) {
|
||||
LOGE("Key ID not found.");
|
||||
LOGE("Key ID not found");
|
||||
return OEMCrypto_ERROR_NO_CONTENT_KEY;
|
||||
}
|
||||
|
||||
@@ -857,7 +1009,7 @@ OEMCryptoResult SessionContext::RefreshKey(
|
||||
|
||||
KeyControlBlock key_control_block(control);
|
||||
if (!key_control_block.valid()) {
|
||||
LOGE("Error parsing key control.");
|
||||
LOGE("Error parsing key control");
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
if ((key_control_block.control_bits() & wvoec::kControlNonceEnabled) &&
|
||||
@@ -920,7 +1072,7 @@ OEMCryptoResult SessionContext::CheckKeyUse(const std::string& log_string,
|
||||
OEMCryptoBufferType buffer_type) {
|
||||
const KeyControlBlock& control = current_content_key()->control();
|
||||
if (use_type && (!(control.control_bits() & use_type))) {
|
||||
LOGE("[%s(): control bit says not allowed.", log_string.c_str());
|
||||
LOGE("[%s(): control bit says not allowed", log_string.c_str());
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
if (control.control_bits() & wvoec::kControlDataPathSecure) {
|
||||
@@ -938,7 +1090,9 @@ OEMCryptoResult SessionContext::CheckKeyUse(const std::string& log_string,
|
||||
}
|
||||
if (control.duration() > 0) {
|
||||
if (control.duration() < CurrentTimer()) {
|
||||
LOGE("[%s(): key expired.", log_string.c_str());
|
||||
LOGE("%s: key expired. duration=%d, timer=%d, now=%ld",
|
||||
log_string.c_str(), control.duration(), CurrentTimer(),
|
||||
ce_->OnlineTime());
|
||||
return OEMCrypto_ERROR_KEY_EXPIRED;
|
||||
}
|
||||
}
|
||||
@@ -965,18 +1119,18 @@ OEMCryptoResult SessionContext::CheckKeyUse(const std::string& log_string,
|
||||
if ((control.control_bits() & wvoec::kControlDisableAnalogOutput) &&
|
||||
(ce_->analog_display_active() ||
|
||||
(buffer_type == OEMCrypto_BufferType_Clear))) {
|
||||
LOGE("[%s(): control bit says disable analog.", log_string.c_str());
|
||||
LOGE("[%s(): control bit says disable analog", log_string.c_str());
|
||||
return OEMCrypto_ERROR_ANALOG_OUTPUT;
|
||||
}
|
||||
// Check if CGMS is required.
|
||||
if (control.control_bits() & wvoec::kControlCGMSMask) {
|
||||
// We can't control CGMS for a clear buffer.
|
||||
if (buffer_type == OEMCrypto_BufferType_Clear) {
|
||||
LOGE("[%s(): CGMS required, but buffer is clear.", log_string.c_str());
|
||||
LOGE("[%s(): CGMS required, but buffer is clear", log_string.c_str());
|
||||
return OEMCrypto_ERROR_ANALOG_OUTPUT;
|
||||
}
|
||||
if ( ce_->analog_display_active() && !ce_->cgms_a_active()) {
|
||||
LOGE("[%s(): control bit says CGMS required.", log_string.c_str());
|
||||
LOGE("[%s(): control bit says CGMS required", log_string.c_str());
|
||||
return OEMCrypto_ERROR_ANALOG_OUTPUT;
|
||||
}
|
||||
}
|
||||
@@ -1003,11 +1157,11 @@ OEMCryptoResult SessionContext::Generic_Encrypt(const uint8_t* in_buffer,
|
||||
OEMCrypto_BufferType_Clear);
|
||||
if (result != OEMCrypto_SUCCESS) return result;
|
||||
if (algorithm != OEMCrypto_AES_CBC_128_NO_PADDING) {
|
||||
LOGE("[Generic_Encrypt(): algorithm bad.");
|
||||
LOGE("[Generic_Encrypt(): algorithm bad");
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
if (buffer_length % AES_BLOCK_SIZE != 0) {
|
||||
LOGE("[Generic_Encrypt(): buffers size bad.");
|
||||
LOGE("[Generic_Encrypt(): buffers size bad");
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
const uint8_t* key_u8 = &key[0];
|
||||
@@ -1036,7 +1190,7 @@ OEMCryptoResult SessionContext::Generic_Decrypt(const uint8_t* in_buffer,
|
||||
const std::vector<uint8_t>& key = current_content_key()->value();
|
||||
// Set the AES key.
|
||||
if (static_cast<int>(key.size()) != AES_BLOCK_SIZE) {
|
||||
LOGE("[Generic_Decrypt(): CONTENT_KEY has wrong size.");
|
||||
LOGE("[Generic_Decrypt(): CONTENT_KEY has wrong size");
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
OEMCryptoResult result = CheckKeyUse("Generic_Decrypt", wvoec::kControlAllowDecrypt,
|
||||
@@ -1044,11 +1198,11 @@ OEMCryptoResult SessionContext::Generic_Decrypt(const uint8_t* in_buffer,
|
||||
if (result != OEMCrypto_SUCCESS) return result;
|
||||
|
||||
if (algorithm != OEMCrypto_AES_CBC_128_NO_PADDING) {
|
||||
LOGE("[Generic_Decrypt(): bad algorithm.");
|
||||
LOGE("[Generic_Decrypt(): bad algorithm");
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
if (buffer_length % AES_BLOCK_SIZE != 0) {
|
||||
LOGE("[Generic_Decrypt(): bad buffer size.");
|
||||
LOGE("[Generic_Decrypt(): bad buffer size");
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
const uint8_t* key_u8 = &key[0];
|
||||
@@ -1076,7 +1230,7 @@ OEMCryptoResult SessionContext::Generic_Sign(const uint8_t* in_buffer,
|
||||
}
|
||||
if (*signature_length < SHA256_DIGEST_LENGTH) {
|
||||
*signature_length = SHA256_DIGEST_LENGTH;
|
||||
LOGE("[Generic_Sign(): bad signature length.");
|
||||
LOGE("[Generic_Sign(): bad signature length");
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
const std::vector<uint8_t>& key = current_content_key()->value();
|
||||
@@ -1088,7 +1242,7 @@ OEMCryptoResult SessionContext::Generic_Sign(const uint8_t* in_buffer,
|
||||
OEMCrypto_BufferType_Clear);
|
||||
if (result != OEMCrypto_SUCCESS) return result;
|
||||
if (algorithm != OEMCrypto_HMAC_SHA256) {
|
||||
LOGE("[Generic_Sign(): bad algorithm.");
|
||||
LOGE("[Generic_Sign(): bad algorithm");
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
unsigned int md_len = *signature_length;
|
||||
@@ -1097,7 +1251,7 @@ OEMCryptoResult SessionContext::Generic_Sign(const uint8_t* in_buffer,
|
||||
*signature_length = md_len;
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
LOGE("[Generic_Sign(): hmac failed.");
|
||||
LOGE("[Generic_Sign(): hmac failed");
|
||||
dump_boringssl_error();
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
@@ -1124,7 +1278,7 @@ OEMCryptoResult SessionContext::Generic_Verify(const uint8_t* in_buffer,
|
||||
OEMCrypto_BufferType_Clear);
|
||||
if (result != OEMCrypto_SUCCESS) return result;
|
||||
if (algorithm != OEMCrypto_HMAC_SHA256) {
|
||||
LOGE("[Generic_Verify(): bad algorithm.");
|
||||
LOGE("[Generic_Verify(): bad algorithm");
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
unsigned int md_len = signature_length;
|
||||
@@ -1137,7 +1291,7 @@ OEMCryptoResult SessionContext::Generic_Verify(const uint8_t* in_buffer,
|
||||
return OEMCrypto_ERROR_SIGNATURE_FAILURE;
|
||||
}
|
||||
}
|
||||
LOGE("[Generic_Verify(): HMAC failed.");
|
||||
LOGE("[Generic_Verify(): HMAC failed");
|
||||
dump_boringssl_error();
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
@@ -1176,7 +1330,7 @@ bool SessionContext::QueryKeyControlBlock(const KeyId& key_id, uint32_t* data) {
|
||||
OEMCryptoResult SessionContext::SelectContentKey(
|
||||
const KeyId& key_id, OEMCryptoCipherMode cipher_mode) {
|
||||
if (session_keys_ == nullptr) {
|
||||
LOGE("Select Key: no session keys.");
|
||||
LOGE("Select Key: no session keys");
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
Key* content_key = session_keys_->Find(key_id);
|
||||
@@ -1190,8 +1344,8 @@ OEMCryptoResult SessionContext::SelectContentKey(
|
||||
|
||||
if (control.duration() > 0) {
|
||||
if (control.duration() < CurrentTimer()) {
|
||||
LOGE("[SelectContentKey(): KEY_EXPIRED %d versus %d]", control.duration(),
|
||||
CurrentTimer());
|
||||
LOGE("KEY_EXPIRED duration=%d, timer=%d, now=%ld", control.duration(),
|
||||
CurrentTimer(), ce_->OnlineTime());
|
||||
return OEMCrypto_ERROR_KEY_EXPIRED;
|
||||
}
|
||||
}
|
||||
@@ -1207,7 +1361,7 @@ OEMCryptoResult SessionContext::CreateNewUsageEntry(
|
||||
uint32_t* usage_entry_number) {
|
||||
if (usage_entry_) {
|
||||
// Can only load one entry per session.
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
return OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES;
|
||||
}
|
||||
OEMCryptoResult result = ce_->usage_table().CreateNewUsageEntry(
|
||||
this, &usage_entry_, usage_entry_number);
|
||||
@@ -1221,7 +1375,7 @@ OEMCryptoResult SessionContext::LoadUsageEntry(
|
||||
uint32_t index, const std::vector<uint8_t>& buffer) {
|
||||
if (usage_entry_) {
|
||||
// Can only load one entry per session.
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
return OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES;
|
||||
}
|
||||
OEMCryptoResult result =
|
||||
ce_->usage_table().LoadUsageEntry(this, &usage_entry_, index, buffer);
|
||||
@@ -1243,7 +1397,7 @@ OEMCryptoResult SessionContext::UpdateUsageEntry(uint8_t* header_buffer,
|
||||
uint8_t* entry_buffer,
|
||||
size_t* entry_buffer_length) {
|
||||
if (!usage_entry_) {
|
||||
LOGE("UpdateUsageEntry: Session has no entry.");
|
||||
LOGE("UpdateUsageEntry: Session has no entry");
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
return ce_->usage_table().UpdateUsageEntry(this, usage_entry_, header_buffer,
|
||||
@@ -1290,11 +1444,12 @@ bool SessionContext::DecryptMessage(const std::vector<uint8_t>& key,
|
||||
return true;
|
||||
}
|
||||
|
||||
OEMCryptoResult SessionContext::DecryptCENC(
|
||||
OEMCryptoResult SessionContext::DecryptCENC_V15(
|
||||
const uint8_t* iv, size_t block_offset,
|
||||
const OEMCrypto_CENCEncryptPatternDesc* pattern, const uint8_t* cipher_data,
|
||||
size_t cipher_data_length, bool is_encrypted, uint8_t* clear_data,
|
||||
OEMCryptoBufferType buffer_type, uint8_t subsample_flags) {
|
||||
const OEMCrypto_CENCEncryptPatternDesc_V15* pattern,
|
||||
const uint8_t* cipher_data, size_t cipher_data_length, bool is_encrypted,
|
||||
uint8_t* clear_data, OEMCryptoBufferType buffer_type,
|
||||
uint8_t subsample_flags) {
|
||||
OEMCryptoResult result =
|
||||
ChooseDecrypt(iv, block_offset, pattern, cipher_data, cipher_data_length,
|
||||
is_encrypted, clear_data, buffer_type);
|
||||
@@ -1332,9 +1487,9 @@ OEMCryptoResult SessionContext::DecryptCENC(
|
||||
|
||||
OEMCryptoResult SessionContext::ChooseDecrypt(
|
||||
const uint8_t* iv, size_t block_offset,
|
||||
const OEMCrypto_CENCEncryptPatternDesc* pattern, const uint8_t* cipher_data,
|
||||
size_t cipher_data_length, bool is_encrypted, uint8_t* clear_data,
|
||||
OEMCryptoBufferType buffer_type) {
|
||||
const OEMCrypto_CENCEncryptPatternDesc_V15* pattern,
|
||||
const uint8_t* cipher_data, size_t cipher_data_length, bool is_encrypted,
|
||||
uint8_t* clear_data, OEMCryptoBufferType buffer_type) {
|
||||
// If the data is clear, we do not need a current key selected.
|
||||
if (!is_encrypted) {
|
||||
if (buffer_type != OEMCrypto_BufferType_Direct) {
|
||||
@@ -1384,8 +1539,9 @@ OEMCryptoResult SessionContext::ChooseDecrypt(
|
||||
|
||||
OEMCryptoResult SessionContext::DecryptCBC(
|
||||
const uint8_t* key, const uint8_t* initial_iv,
|
||||
const OEMCrypto_CENCEncryptPatternDesc* pattern, const uint8_t* cipher_data,
|
||||
size_t cipher_data_length, uint8_t* clear_data) {
|
||||
const OEMCrypto_CENCEncryptPatternDesc_V15* pattern,
|
||||
const uint8_t* cipher_data, size_t cipher_data_length,
|
||||
uint8_t* clear_data) {
|
||||
AES_KEY aes_key;
|
||||
AES_set_decrypt_key(&key[0], AES_BLOCK_SIZE * 8, &aes_key);
|
||||
uint8_t iv[AES_BLOCK_SIZE];
|
||||
@@ -1393,7 +1549,8 @@ OEMCryptoResult SessionContext::DecryptCBC(
|
||||
memcpy(iv, &initial_iv[0], AES_BLOCK_SIZE);
|
||||
|
||||
size_t l = 0;
|
||||
size_t pattern_offset = pattern->offset;
|
||||
// TODO(b/135285640): remove this.
|
||||
size_t pattern_offset = 0;
|
||||
while (l < cipher_data_length) {
|
||||
size_t size =
|
||||
std::min(cipher_data_length - l, static_cast<size_t>(AES_BLOCK_SIZE));
|
||||
@@ -1423,15 +1580,17 @@ OEMCryptoResult SessionContext::DecryptCBC(
|
||||
|
||||
OEMCryptoResult SessionContext::PatternDecryptCTR(
|
||||
const uint8_t* key, const uint8_t* initial_iv, size_t block_offset,
|
||||
const OEMCrypto_CENCEncryptPatternDesc* pattern, const uint8_t* cipher_data,
|
||||
size_t cipher_data_length, uint8_t* clear_data) {
|
||||
const OEMCrypto_CENCEncryptPatternDesc_V15* pattern,
|
||||
const uint8_t* cipher_data, size_t cipher_data_length,
|
||||
uint8_t* clear_data) {
|
||||
AES_KEY aes_key;
|
||||
AES_set_encrypt_key(&key[0], AES_BLOCK_SIZE * 8, &aes_key);
|
||||
uint8_t iv[AES_BLOCK_SIZE];
|
||||
memcpy(iv, &initial_iv[0], AES_BLOCK_SIZE);
|
||||
|
||||
size_t l = 0;
|
||||
size_t pattern_offset = pattern->offset;
|
||||
// TODO(b/135285640): remove this.
|
||||
size_t pattern_offset = 0;
|
||||
while (l < cipher_data_length) {
|
||||
size_t size =
|
||||
std::min(cipher_data_length - l, AES_BLOCK_SIZE - block_offset);
|
||||
@@ -1579,8 +1738,9 @@ OEMCryptoResult SessionContext::GetHashErrorCode(
|
||||
|
||||
bool SessionContext::set_nonce(uint32_t nonce) {
|
||||
if (state_nonce_created_) return false;
|
||||
if (nonce == 0) return false;
|
||||
state_nonce_created_ = true;
|
||||
nonce_ = nonce;
|
||||
nonce_values_.nonce = nonce;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -62,25 +62,8 @@ class SessionContext {
|
||||
SessionContext() {}
|
||||
|
||||
public:
|
||||
SessionContext(CryptoEngine* ce, SessionId sid, const RSA_shared_ptr& rsa_key)
|
||||
: valid_(true),
|
||||
ce_(ce),
|
||||
id_(sid),
|
||||
current_content_key_(nullptr),
|
||||
session_keys_(nullptr),
|
||||
nonce_(0),
|
||||
rsa_key_(rsa_key),
|
||||
allowed_schemes_(kSign_RSASSA_PSS),
|
||||
usage_entry_(nullptr),
|
||||
srm_requirements_status_(NoSRMVersion),
|
||||
usage_entry_status_(kNoUsageEntry),
|
||||
compute_hash_(false),
|
||||
current_hash_(0),
|
||||
bad_frame_number_(0),
|
||||
hash_error_(OEMCrypto_SUCCESS),
|
||||
state_nonce_created_(false),
|
||||
state_request_signed_(false),
|
||||
state_response_loaded_(false) {}
|
||||
SessionContext(CryptoEngine* ce, SessionId sid,
|
||||
const RSA_shared_ptr& rsa_key);
|
||||
virtual ~SessionContext();
|
||||
|
||||
bool isValid() { return valid_; }
|
||||
@@ -91,10 +74,21 @@ class SessionContext {
|
||||
virtual bool RSADeriveKeys(const std::vector<uint8_t>& enc_session_key,
|
||||
const std::vector<uint8_t>& mac_context,
|
||||
const std::vector<uint8_t>& enc_context);
|
||||
// TODO(b/135288022): remove renewal_message hack.
|
||||
virtual bool GenerateSignature(const uint8_t* message, size_t message_length,
|
||||
uint8_t* signature, size_t* signature_length,
|
||||
bool renewal_message);
|
||||
virtual OEMCryptoResult PrepAndSignLicenseRequest(uint8_t* message,
|
||||
size_t message_length,
|
||||
size_t* core_message_length,
|
||||
uint8_t* signature,
|
||||
size_t* signature_length);
|
||||
virtual OEMCryptoResult PrepAndSignRenewalRequest(uint8_t* message,
|
||||
size_t message_length,
|
||||
size_t* core_message_length,
|
||||
uint8_t* signature,
|
||||
size_t* signature_length);
|
||||
virtual OEMCryptoResult PrepAndSignProvisioningRequest(
|
||||
uint8_t* message, size_t message_length, size_t* core_message_length,
|
||||
uint8_t* signature, size_t* signature_length);
|
||||
// The size of an RSA signature. This is used when signing as a CAST
|
||||
// receiver.
|
||||
size_t RSASignatureSize();
|
||||
virtual OEMCryptoResult GenerateRSASignature(
|
||||
const uint8_t* message, size_t message_length, uint8_t* signature,
|
||||
@@ -102,13 +96,12 @@ class SessionContext {
|
||||
virtual bool ValidateMessage(const uint8_t* message, size_t message_length,
|
||||
const uint8_t* signature,
|
||||
size_t signature_length);
|
||||
OEMCryptoResult DecryptCENC(const uint8_t* iv, size_t block_offset,
|
||||
const OEMCrypto_CENCEncryptPatternDesc* pattern,
|
||||
const uint8_t* cipher_data,
|
||||
size_t cipher_data_length, bool is_encrypted,
|
||||
uint8_t* clear_data,
|
||||
OEMCryptoBufferType buffer_type,
|
||||
uint8_t subsample_flags);
|
||||
OEMCryptoResult DecryptCENC_V15(
|
||||
const uint8_t* iv, size_t block_offset,
|
||||
const OEMCrypto_CENCEncryptPatternDesc_V15* pattern,
|
||||
const uint8_t* cipher_data, size_t cipher_data_length, bool is_encrypted,
|
||||
uint8_t* clear_data, OEMCryptoBufferType buffer_type,
|
||||
uint8_t subsample_flags);
|
||||
|
||||
OEMCryptoResult Generic_Encrypt(const uint8_t* in_buffer,
|
||||
size_t buffer_length, const uint8_t* iv,
|
||||
@@ -135,9 +128,8 @@ class SessionContext {
|
||||
OEMCrypto_Substring srm_restriction_data,
|
||||
OEMCrypto_LicenseType license_type);
|
||||
virtual OEMCryptoResult LoadEntitledContentKeys(
|
||||
const uint8_t* message, size_t message_length,
|
||||
const OEMCrypto_EntitledContentKeyObject* key_array,
|
||||
size_t key_array_length);
|
||||
const uint8_t* message, size_t message_length, size_t key_array_length,
|
||||
const OEMCrypto_EntitledContentKeyObject* key_array);
|
||||
virtual OEMCryptoResult InstallKey(
|
||||
const KeyId& key_id, const std::vector<uint8_t>& key_data,
|
||||
const std::vector<uint8_t>& key_data_iv,
|
||||
@@ -180,10 +172,11 @@ class SessionContext {
|
||||
|
||||
// Return true if nonce was set.
|
||||
bool set_nonce(uint32_t nonce);
|
||||
uint32_t nonce() const { return nonce_; }
|
||||
uint32_t nonce() const { return nonce_values_.nonce; }
|
||||
ODK_NonceValues& nonce_values() { return nonce_values_; }
|
||||
|
||||
bool CheckNonce(uint32_t nonce) const {
|
||||
return nonce != 0 && nonce == nonce_;
|
||||
return nonce != 0 && nonce == nonce_values_.nonce;
|
||||
};
|
||||
|
||||
virtual OEMCryptoResult CreateNewUsageEntry(uint32_t* usage_entry_number);
|
||||
@@ -199,6 +192,18 @@ class SessionContext {
|
||||
OEMCryptoResult MoveEntry(uint32_t new_index);
|
||||
|
||||
protected:
|
||||
// Signature size of the currently loaded private key.
|
||||
size_t CertSignatureSize();
|
||||
// Signature size when using a keybox or OEM Cert's private key.
|
||||
size_t ROTSignatureSize();
|
||||
virtual OEMCryptoResult GenerateCertSignature(const uint8_t* message,
|
||||
size_t message_length,
|
||||
uint8_t* signature,
|
||||
size_t* signature_length);
|
||||
virtual OEMCryptoResult GenerateSignature(const uint8_t* message,
|
||||
size_t message_length,
|
||||
uint8_t* signature,
|
||||
size_t* signature_length);
|
||||
bool DeriveKey(const std::vector<uint8_t>& key,
|
||||
const std::vector<uint8_t>& context, int counter,
|
||||
std::vector<uint8_t>* out);
|
||||
@@ -217,19 +222,19 @@ class SessionContext {
|
||||
OEMCryptoResult CheckStatusOnline(uint32_t nonce, uint32_t control);
|
||||
// Check that the usage entry status is valid for offline use.
|
||||
OEMCryptoResult CheckStatusOffline(uint32_t nonce, uint32_t control);
|
||||
OEMCryptoResult ChooseDecrypt(const uint8_t* iv, size_t block_offset,
|
||||
const OEMCrypto_CENCEncryptPatternDesc* pattern,
|
||||
const uint8_t* cipher_data,
|
||||
size_t cipher_data_length, bool is_encrypted,
|
||||
uint8_t* clear_data,
|
||||
OEMCryptoBufferType buffer_type);
|
||||
OEMCryptoResult DecryptCBC(const uint8_t* key, const uint8_t* iv,
|
||||
const OEMCrypto_CENCEncryptPatternDesc* pattern,
|
||||
const uint8_t* cipher_data,
|
||||
size_t cipher_data_length, uint8_t* clear_data);
|
||||
OEMCryptoResult ChooseDecrypt(
|
||||
const uint8_t* iv, size_t block_offset,
|
||||
const OEMCrypto_CENCEncryptPatternDesc_V15* pattern,
|
||||
const uint8_t* cipher_data, size_t cipher_data_length, bool is_encrypted,
|
||||
uint8_t* clear_data, OEMCryptoBufferType buffer_type);
|
||||
OEMCryptoResult DecryptCBC(
|
||||
const uint8_t* key, const uint8_t* iv,
|
||||
const OEMCrypto_CENCEncryptPatternDesc_V15* pattern,
|
||||
const uint8_t* cipher_data, size_t cipher_data_length,
|
||||
uint8_t* clear_data);
|
||||
OEMCryptoResult PatternDecryptCTR(
|
||||
const uint8_t* key, const uint8_t* iv, size_t block_offset,
|
||||
const OEMCrypto_CENCEncryptPatternDesc* pattern,
|
||||
const OEMCrypto_CENCEncryptPatternDesc_V15* pattern,
|
||||
const uint8_t* cipher_data, size_t cipher_data_length,
|
||||
uint8_t* clear_data);
|
||||
OEMCryptoResult DecryptCTR(const uint8_t* key_u8, const uint8_t* iv,
|
||||
@@ -250,10 +255,12 @@ class SessionContext {
|
||||
std::vector<uint8_t> session_key_;
|
||||
const Key* current_content_key_;
|
||||
SessionContextKeys* session_keys_;
|
||||
uint32_t nonce_;
|
||||
ODK_NonceValues nonce_values_;
|
||||
RSA_shared_ptr rsa_key_;
|
||||
uint32_t allowed_schemes_; // for RSA signatures.
|
||||
time_t timer_start_;
|
||||
int64_t timer_start_; // TODO(b/140764222): delete.
|
||||
ODK_TimerLimits timer_limits_;
|
||||
ODK_ClockValues clock_values_;
|
||||
UsageTableEntry* usage_entry_;
|
||||
SRMVersionStatus srm_requirements_status_;
|
||||
enum UsageEntryStatus {
|
||||
|
||||
@@ -76,7 +76,6 @@ void DeviceFeatures::Initialize(bool is_cast_receiver,
|
||||
printf("OEMCrypto_Initialize failed. All tests will fail.\n");
|
||||
return;
|
||||
}
|
||||
uint32_t nonce = 0;
|
||||
uint8_t buffer[1];
|
||||
size_t size = 0;
|
||||
provisioning_method = OEMCrypto_GetProvisioningMethod();
|
||||
@@ -92,14 +91,10 @@ void DeviceFeatures::Initialize(bool is_cast_receiver,
|
||||
}
|
||||
// If the device uses a keybox, check to see if loading a certificate is
|
||||
// installed.
|
||||
if (provisioning_method == OEMCrypto_Keybox) {
|
||||
loads_certificate =
|
||||
(OEMCrypto_ERROR_NOT_IMPLEMENTED !=
|
||||
OEMCrypto_RewrapDeviceRSAKey(session, buffer, 0, buffer, 0, &nonce,
|
||||
buffer, 0, buffer, buffer, &size));
|
||||
} else if (provisioning_method == OEMCrypto_OEMCertificate) {
|
||||
// If the device says it uses Provisioning 3.0, then it should be able to
|
||||
// load a DRM certificate. These devices must support RewrapDeviceRSAKey30.
|
||||
if (provisioning_method == OEMCrypto_Keybox ||
|
||||
provisioning_method == OEMCrypto_OEMCertificate) {
|
||||
// Devices with a keybox or OEM Certificate are required to support loading
|
||||
// a DRM certificate.
|
||||
loads_certificate = true;
|
||||
} else {
|
||||
// Other devices are either broken, or they have a baked in certificate.
|
||||
|
||||
@@ -137,7 +137,7 @@ void KeyDeriver::set_mac_keys(const uint8_t* mac_keys) {
|
||||
}
|
||||
|
||||
void KeyDeriver::ServerSignBuffer(const uint8_t* data, size_t data_length,
|
||||
std::vector<uint8_t>* signature) {
|
||||
std::vector<uint8_t>* signature) const {
|
||||
ASSERT_LE(data_length, kMaxMessageSize);
|
||||
ASSERT_EQ(mac_key_server_.size(), MAC_KEY_SIZE);
|
||||
signature->assign(SHA256_DIGEST_LENGTH, 0);
|
||||
@@ -147,7 +147,7 @@ void KeyDeriver::ServerSignBuffer(const uint8_t* data, size_t data_length,
|
||||
}
|
||||
|
||||
void KeyDeriver::ClientSignBuffer(const vector<uint8_t>& buffer,
|
||||
std::vector<uint8_t>* signature) {
|
||||
std::vector<uint8_t>* signature) const {
|
||||
ASSERT_EQ(mac_key_client_.size(), MAC_KEY_SIZE);
|
||||
signature->assign(SHA256_DIGEST_LENGTH, 0);
|
||||
unsigned int sig_len = SHA256_DIGEST_LENGTH;
|
||||
@@ -156,7 +156,7 @@ void KeyDeriver::ClientSignBuffer(const vector<uint8_t>& buffer,
|
||||
}
|
||||
|
||||
void KeyDeriver::ClientSignPstReport(const vector<uint8_t>& pst_report_buffer,
|
||||
std::vector<uint8_t>* signature) {
|
||||
std::vector<uint8_t>* signature) const {
|
||||
ASSERT_EQ(mac_key_client_.size(), MAC_KEY_SIZE);
|
||||
signature->assign(SHA_DIGEST_LENGTH, 0);
|
||||
unsigned int sig_len = SHA_DIGEST_LENGTH;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
namespace wvoec {
|
||||
|
||||
constexpr size_t kMaxTestRSAKeyLength = 2000; // Rough estimate.
|
||||
constexpr size_t kMaxCoreProvRequest = 150; // Rough estimate.
|
||||
|
||||
// This structure will be signed to simulate a provisioning response from the
|
||||
// server.
|
||||
@@ -25,6 +26,8 @@ struct RSAPrivateKeyMessage {
|
||||
uint8_t rsa_key[kMaxTestRSAKeyLength];
|
||||
uint8_t rsa_key_iv[KEY_IV_SIZE];
|
||||
size_t rsa_key_length;
|
||||
uint8_t enc_message_key[kMaxTestRSAKeyLength];
|
||||
size_t enc_message_key_length;
|
||||
uint32_t nonce;
|
||||
};
|
||||
|
||||
@@ -38,7 +41,7 @@ class Encryptor {
|
||||
void set_enc_key(const std::vector<uint8_t>& enc_key);
|
||||
|
||||
// This encrypts an RSAPrivateKeyMessage with encryption_key so that it may be
|
||||
// loaded with OEMCrypto_RewrapDeviceRSAKey.
|
||||
// loaded with OEMCrypto_LoadProvisioningResponse.
|
||||
// This modifies the clear data: it adds padding and generates a random iv.
|
||||
void PadAndEncryptProvisioningMessage(RSAPrivateKeyMessage* data,
|
||||
RSAPrivateKeyMessage* encrypted) const;
|
||||
@@ -64,16 +67,16 @@ class KeyDeriver : public Encryptor {
|
||||
const std::vector<uint8_t>& enc_key_context);
|
||||
// Sign the buffer with server's mac key.
|
||||
void ServerSignBuffer(const uint8_t* data, size_t data_length,
|
||||
std::vector<uint8_t>* signature);
|
||||
std::vector<uint8_t>* signature) const;
|
||||
// Sign the buffer with client's known mac key. Known test keys must be
|
||||
// installed first. This uses HMAC with SHA256, so is suitable for a message.
|
||||
void ClientSignBuffer(const std::vector<uint8_t>& buffer,
|
||||
std::vector<uint8_t>* signature);
|
||||
std::vector<uint8_t>* signature) const;
|
||||
// Sign the pst buffer with client's known mac key. Known test keys must be
|
||||
// installed first. This uses HMAC with SHA128, and skips the beginning of the
|
||||
// buffer, so is only suitable for a pst report.
|
||||
void ClientSignPstReport(const std::vector<uint8_t>& pst_report_buffer,
|
||||
std::vector<uint8_t>* signature);
|
||||
std::vector<uint8_t>* signature) const;
|
||||
void set_mac_keys(const uint8_t* mac_keys);
|
||||
|
||||
private:
|
||||
|
||||
@@ -25,13 +25,16 @@
|
||||
#include <vector>
|
||||
|
||||
#include "OEMCryptoCENC.h"
|
||||
#include "clock.h"
|
||||
#include "disallow_copy_and_assign.h"
|
||||
#include "log.h"
|
||||
#include "oec_device_features.h"
|
||||
#include "oec_test_data.h"
|
||||
#include "oec_util.h"
|
||||
#include "oemcrypto_types.h"
|
||||
#include "platform.h"
|
||||
#include "string_conversions.h"
|
||||
#include "test_sleep.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
@@ -118,19 +121,207 @@ OEMCrypto_Substring GetSubstring(const std::string& message,
|
||||
return substring;
|
||||
}
|
||||
|
||||
Test_PST_Report::Test_PST_Report(const std::string& pst_in,
|
||||
OEMCrypto_Usage_Entry_Status status_in)
|
||||
: status(status_in), pst(pst_in) {
|
||||
time_created = wvcdm::Clock().GetCurrentTime();
|
||||
}
|
||||
|
||||
template <class CoreRequest, PrepAndSignRequest_t PrepAndSignRequest,
|
||||
class CoreResponse, class ResponseData>
|
||||
void RoundTrip<CoreRequest, PrepAndSignRequest, CoreResponse,
|
||||
ResponseData>::SignAndVerifyRequest() {
|
||||
// In the real world, a message should be signed by the client and
|
||||
// verified by the server. This simulates that.
|
||||
vector<uint8_t> data(message_size_);
|
||||
for (size_t i = 0; i < data.size(); i++) data[i] = i & 0xFF;
|
||||
OEMCryptoResult sts;
|
||||
size_t gen_signature_length = 0;
|
||||
size_t core_message_length = 0;
|
||||
sts =
|
||||
PrepAndSignRequest(session()->session_id(), data.data(), data.size(),
|
||||
&core_message_length, nullptr, &gen_signature_length);
|
||||
ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts);
|
||||
vector<uint8_t> gen_signature(gen_signature_length);
|
||||
sts = PrepAndSignRequest(session()->session_id(), data.data(), data.size(),
|
||||
&core_message_length, gen_signature.data(),
|
||||
&gen_signature_length);
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
||||
if (global_features.api_version >= 16) {
|
||||
ASSERT_GT(data.size(), core_message_length);
|
||||
std::string core_message(reinterpret_cast<char*>(data.data()),
|
||||
core_message_length);
|
||||
FillAndVerifyCoreRequest(core_message);
|
||||
}
|
||||
VerifyRequestSignature(data, gen_signature);
|
||||
}
|
||||
|
||||
template <class CoreRequest, PrepAndSignRequest_t PrepAndSignRequest,
|
||||
class CoreResponse, class ResponseData>
|
||||
OEMCrypto_Substring RoundTrip<CoreRequest, PrepAndSignRequest, CoreResponse,
|
||||
ResponseData>::FindSubstring(const void* pointer,
|
||||
size_t length) {
|
||||
OEMCrypto_Substring substring;
|
||||
if (length == 0 || pointer == nullptr) {
|
||||
substring.offset = 0;
|
||||
substring.length = 0;
|
||||
} else {
|
||||
// TODO(b/143850949): This adds the core message size to the offset so
|
||||
// that it can be used more easily. This computation should be done in the
|
||||
// ODK parse function? To be discussed.
|
||||
substring.offset = reinterpret_cast<const uint8_t*>(pointer) -
|
||||
reinterpret_cast<const uint8_t*>(&response_data_) +
|
||||
serialized_core_message_.size();
|
||||
substring.length = length;
|
||||
}
|
||||
return substring;
|
||||
}
|
||||
|
||||
void ProvisioningRoundTrip::PrepareSession(
|
||||
const wvoec::WidevineKeybox& keybox) {
|
||||
ASSERT_NO_FATAL_FAILURE(session_->open());
|
||||
session_->GenerateNonce();
|
||||
if (global_features.provisioning_method == OEMCrypto_Keybox) {
|
||||
session_->GenerateDerivedKeysFromKeybox(keybox);
|
||||
encryptor_ = session_->key_deriver();
|
||||
} else {
|
||||
EXPECT_EQ(global_features.provisioning_method, OEMCrypto_OEMCertificate);
|
||||
session_->LoadOEMCert(true);
|
||||
session_->GenerateRSASessionKey(&message_key_, &encrypted_message_key_);
|
||||
encryptor_.set_enc_key(message_key_);
|
||||
}
|
||||
}
|
||||
|
||||
void ProvisioningRoundTrip::VerifyRequestSignature(
|
||||
const vector<uint8_t>& data, const vector<uint8_t>& generated_signature) {
|
||||
if (global_features.provisioning_method == OEMCrypto_OEMCertificate) {
|
||||
session()->VerifyRSASignature(data, generated_signature.data(),
|
||||
generated_signature.size(), kSign_RSASSA_PSS);
|
||||
} else {
|
||||
EXPECT_EQ(global_features.provisioning_method, OEMCrypto_Keybox);
|
||||
ASSERT_EQ(HMAC_SHA256_SIGNATURE_SIZE, generated_signature.size());
|
||||
std::vector<uint8_t> expected_signature;
|
||||
session()->key_deriver().ClientSignBuffer(data, &expected_signature);
|
||||
ASSERT_EQ(expected_signature, generated_signature);
|
||||
}
|
||||
}
|
||||
|
||||
void ProvisioningRoundTrip::FillAndVerifyCoreRequest(
|
||||
const std::string& core_message_string) {
|
||||
EXPECT_TRUE(
|
||||
oec_util::ParseProvisioningRequest(core_message_string, &core_request_));
|
||||
EXPECT_EQ(global_features.api_version, core_request_.api_version);
|
||||
EXPECT_EQ(session()->nonce(), core_request_.nonce);
|
||||
EXPECT_EQ(session()->session_id(), core_request_.session_id);
|
||||
size_t device_id_length = core_request_.device_id.size();
|
||||
std::vector<uint8_t> device_id(device_id_length);
|
||||
EXPECT_EQ(OEMCrypto_SUCCESS,
|
||||
OEMCrypto_GetDeviceID(device_id.data(), &device_id_length));
|
||||
EXPECT_EQ(core_request_.device_id.size(), device_id_length);
|
||||
std::string device_id_string(reinterpret_cast<char*>(device_id.data()),
|
||||
device_id_length);
|
||||
EXPECT_EQ(device_id_string, core_request_.device_id);
|
||||
}
|
||||
|
||||
void ProvisioningRoundTrip::CreateDefaultResponse() {
|
||||
if (allowed_schemes_ != kSign_RSASSA_PSS) {
|
||||
uint32_t algorithm_n = htonl(allowed_schemes_);
|
||||
memcpy(response_data_.rsa_key, "SIGN", 4);
|
||||
memcpy(response_data_.rsa_key + 4, &algorithm_n, 4);
|
||||
memcpy(response_data_.rsa_key + 8, encoded_rsa_key_.data(),
|
||||
encoded_rsa_key_.size());
|
||||
response_data_.rsa_key_length = 8 + encoded_rsa_key_.size();
|
||||
} else {
|
||||
memcpy(response_data_.rsa_key, encoded_rsa_key_.data(),
|
||||
encoded_rsa_key_.size());
|
||||
response_data_.rsa_key_length = encoded_rsa_key_.size();
|
||||
}
|
||||
response_data_.nonce = session_->nonce();
|
||||
if (encrypted_message_key_.size() > 0) {
|
||||
ASSERT_LE(encrypted_message_key_.size(), kMaxTestRSAKeyLength);
|
||||
memcpy(response_data_.enc_message_key, encrypted_message_key_.data(),
|
||||
encrypted_message_key_.size());
|
||||
response_data_.enc_message_key_length = encrypted_message_key_.size();
|
||||
} else {
|
||||
response_data_.enc_message_key_length = 0;
|
||||
}
|
||||
// TODO(b/143850949): There is a problem that when the offset is
|
||||
// computed, it is based on the protobuf message, but when it is used, we
|
||||
// include the core message. This is a hack around that: we serialize
|
||||
// twice -- once to get the size, and later, to really serialize the data.
|
||||
ASSERT_TRUE(CreateCoreProvisioningResponse(core_response_, core_request_,
|
||||
&serialized_core_message_));
|
||||
// TODO(b/67735947): set key type. use key type.
|
||||
core_response_.key_type = OEMCrypto_Supports_RSA_2048bit;
|
||||
core_response_.enc_private_key =
|
||||
FindSubstring(response_data_.rsa_key, response_data_.rsa_key_length);
|
||||
core_response_.enc_private_key_iv = FindSubstring(
|
||||
response_data_.rsa_key_iv, sizeof(response_data_.rsa_key_iv));
|
||||
core_response_.encrypted_message_key = FindSubstring(
|
||||
response_data_.enc_message_key, response_data_.enc_message_key_length);
|
||||
}
|
||||
|
||||
void ProvisioningRoundTrip::EncryptAndSignResponse() {
|
||||
encryptor_.PadAndEncryptProvisioningMessage(&response_data_,
|
||||
&encrypted_response_data_);
|
||||
core_response_.enc_private_key.length =
|
||||
encrypted_response_data_.rsa_key_length;
|
||||
ASSERT_TRUE(CreateCoreProvisioningResponse(core_response_, core_request_,
|
||||
&serialized_core_message_));
|
||||
// Stripe the encrypted message.
|
||||
encrypted_response_.resize(message_size_);
|
||||
for (size_t i = 0; i < encrypted_response_.size(); i++) {
|
||||
encrypted_response_[i] = i % 0x100;
|
||||
}
|
||||
ASSERT_GE(encrypted_response_.size(), serialized_core_message_.size());
|
||||
memcpy(encrypted_response_.data(), serialized_core_message_.data(),
|
||||
serialized_core_message_.size());
|
||||
ASSERT_GE(encrypted_response_.size(),
|
||||
serialized_core_message_.size() + sizeof(encrypted_response_data_));
|
||||
memcpy(encrypted_response_.data() + serialized_core_message_.size(),
|
||||
reinterpret_cast<const uint8_t*>(&encrypted_response_data_),
|
||||
sizeof(encrypted_response_data_));
|
||||
if (global_features.provisioning_method == OEMCrypto_OEMCertificate) {
|
||||
session()->GenerateDerivedKeysFromSessionKey();
|
||||
}
|
||||
session()->key_deriver().ServerSignBuffer(encrypted_response_.data(),
|
||||
encrypted_response_.size(),
|
||||
&response_signature_);
|
||||
}
|
||||
|
||||
OEMCryptoResult ProvisioningRoundTrip::LoadResponse() {
|
||||
size_t wrapped_key_length = 0;
|
||||
const OEMCryptoResult sts = OEMCrypto_LoadProvisioning(
|
||||
session_->session_id(), encrypted_response_.data(),
|
||||
encrypted_response_.size(), serialized_core_message_.size(),
|
||||
response_signature_.data(), response_signature_.size(), nullptr,
|
||||
&wrapped_key_length);
|
||||
if (sts != OEMCrypto_ERROR_SHORT_BUFFER) return sts;
|
||||
wrapped_rsa_key_.clear();
|
||||
wrapped_rsa_key_.assign(wrapped_key_length, 0);
|
||||
return OEMCrypto_LoadProvisioning(
|
||||
session_->session_id(), encrypted_response_.data(),
|
||||
encrypted_response_.size(), serialized_core_message_.size(),
|
||||
response_signature_.data(), response_signature_.size(),
|
||||
wrapped_rsa_key_.data(), &wrapped_key_length);
|
||||
}
|
||||
|
||||
void ProvisioningRoundTrip::VerifyLoadFailed() {
|
||||
if (wrapped_rsa_key_.size() == 0) return;
|
||||
std::vector<uint8_t> zero(wrapped_rsa_key_.size(), 0);
|
||||
ASSERT_EQ(zero, wrapped_rsa_key_);
|
||||
}
|
||||
|
||||
Session::Session()
|
||||
: open_(false),
|
||||
forced_session_id_(false),
|
||||
session_id_(0),
|
||||
nonce_(0),
|
||||
public_rsa_(0),
|
||||
message_size_(sizeof(MessageData)),
|
||||
// Most tests only use 4 keys. Other tests will explicitly call
|
||||
// set_num_keys.
|
||||
num_keys_(4) {
|
||||
// Stripe the padded message.
|
||||
for (size_t i = 0; i < sizeof(padded_message_.padding); i++) {
|
||||
padded_message_.padding[i] = i % 0x100;
|
||||
}
|
||||
}
|
||||
|
||||
Session::~Session() {
|
||||
@@ -168,7 +359,7 @@ void Session::GenerateNonce(int* error_counter) {
|
||||
if (error_counter) {
|
||||
(*error_counter)++;
|
||||
} else {
|
||||
sleep(1); // wait a second, then try again.
|
||||
wvcdm::TestSleep::Sleep(1); // wait a second, then try again.
|
||||
// The following is after a 1 second pause, so it cannot be from a nonce
|
||||
// flood.
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS,
|
||||
@@ -371,8 +562,8 @@ void Session::LoadEntitledContentKeys(OEMCryptoResult expected_sts) {
|
||||
session_id(),
|
||||
reinterpret_cast<const uint8_t*>(encrypted_entitled_message_.data()),
|
||||
encrypted_entitled_message_.size(),
|
||||
encrypted_entitled_key_array.data(),
|
||||
encrypted_entitled_key_array.size()));
|
||||
encrypted_entitled_key_array.size(),
|
||||
encrypted_entitled_key_array.data()));
|
||||
if (expected_sts != OEMCrypto_SUCCESS) {
|
||||
return;
|
||||
}
|
||||
@@ -454,12 +645,12 @@ void Session::RefreshTestKeys(const size_t key_count, uint32_t control_bits,
|
||||
ASSERT_NO_FATAL_FAILURE(TestDecryptCTR());
|
||||
// This should still be valid key, even if the refresh failed, because this
|
||||
// is before the original license duration.
|
||||
sleep(kShortSleep);
|
||||
wvcdm::TestSleep::Sleep(kShortSleep);
|
||||
ASSERT_NO_FATAL_FAILURE(TestDecryptCTR(false));
|
||||
// This should be after duration of the original license, but before the
|
||||
// expiration of the refresh message. This should succeed if and only if the
|
||||
// refresh succeeded.
|
||||
sleep(kShortSleep + kLongSleep);
|
||||
wvcdm::TestSleep::Sleep(kShortSleep + kLongSleep);
|
||||
if (expected_result == OEMCrypto_SUCCESS) {
|
||||
ASSERT_NO_FATAL_FAILURE(TestDecryptCTR(false, OEMCrypto_SUCCESS));
|
||||
} else {
|
||||
@@ -609,6 +800,7 @@ void Session::SetLoadKeysSubstringParams() {
|
||||
}
|
||||
|
||||
void Session::EncryptAndSign() {
|
||||
ASSERT_NO_FATAL_FAILURE(GenerateDerivedKeysFromSessionKey());
|
||||
encrypted_license() = license_;
|
||||
|
||||
uint8_t iv_buffer[16];
|
||||
@@ -644,57 +836,24 @@ void Session::VerifyLicenseRequestSignature(size_t data_length) {
|
||||
for (size_t i = 0; i < data.size(); i++) data[i] = i & 0xFF;
|
||||
OEMCryptoResult sts;
|
||||
size_t gen_signature_length = 0;
|
||||
|
||||
// TODO(b/135288420): Test core message functionality.
|
||||
// This function should be split into three versions, one for each core
|
||||
// message.
|
||||
size_t core_message_length = 0;
|
||||
sts = OEMCrypto_SignLicenseRequest(session_id(), data.data(), data.size(),
|
||||
nullptr, &core_message_length, nullptr,
|
||||
&gen_signature_length);
|
||||
sts = OEMCrypto_PrepAndSignLicenseRequest(session_id(), data.data(),
|
||||
data.size(), &core_message_length,
|
||||
nullptr, &gen_signature_length);
|
||||
ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts);
|
||||
const size_t hmac_signature_size = 32u;
|
||||
ASSERT_EQ(hmac_signature_size, gen_signature_length);
|
||||
vector<uint8_t> gen_signature(gen_signature_length);
|
||||
sts = OEMCrypto_SignLicenseRequest(
|
||||
session_id(), data.data(), data.size(), nullptr, &core_message_length,
|
||||
sts = OEMCrypto_PrepAndSignLicenseRequest(
|
||||
session_id(), data.data(), data.size(), &core_message_length,
|
||||
gen_signature.data(), &gen_signature_length);
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
||||
std::vector<uint8_t> expected_signature;
|
||||
key_deriver_.ClientSignBuffer(data, &expected_signature);
|
||||
ASSERT_EQ(expected_signature, gen_signature);
|
||||
}
|
||||
|
||||
// TODO(b/135288022): This function only handles the keybox case.
|
||||
// It should do something different for Prov 3.0.
|
||||
void Session::VerifyProvisioningRequestSignature(size_t data_length) {
|
||||
// In the real world, a message should be signed by the client and
|
||||
// verified by the server. This simulates that.
|
||||
vector<uint8_t> data(data_length);
|
||||
for (size_t i = 0; i < data.size(); i++) data[i] = i & 0xFF;
|
||||
OEMCryptoResult sts;
|
||||
size_t gen_signature_length = 0;
|
||||
|
||||
// TODO(b/135288420): Test core message functionality.
|
||||
// This function should be split into three versions, one for each core
|
||||
// message.
|
||||
size_t core_message_length = 0;
|
||||
sts = OEMCrypto_SignProvisioningRequest(
|
||||
session_id(), data.data(), data.size(), nullptr, &core_message_length,
|
||||
nullptr, &gen_signature_length);
|
||||
ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts);
|
||||
const size_t hmac_signature_size = 32u;
|
||||
ASSERT_EQ(hmac_signature_size, gen_signature_length);
|
||||
vector<uint8_t> gen_signature(gen_signature_length);
|
||||
// TODO(b/135288022): This function should pick the right type of signature,
|
||||
// and then call SignProvisioningRequest.
|
||||
sts = OEMCrypto_SignLicenseRequest(
|
||||
session_id(), data.data(), data.size(), nullptr, &core_message_length,
|
||||
gen_signature.data(), &gen_signature_length);
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
||||
std::vector<uint8_t> expected_signature;
|
||||
key_deriver_.ClientSignBuffer(data, &expected_signature);
|
||||
ASSERT_EQ(expected_signature, gen_signature);
|
||||
if (global_features.api_version >= 16) {
|
||||
ASSERT_GT(data.size(), core_message_length);
|
||||
std::string core_message(reinterpret_cast<char*>(data.data()),
|
||||
core_message_length);
|
||||
VerifyCoreLicenseRequest(core_message);
|
||||
}
|
||||
VerifyRSASignature(data, gen_signature.data(), gen_signature.size(),
|
||||
kSign_RSASSA_PSS);
|
||||
}
|
||||
|
||||
void Session::VerifyRenewalRequestSignature(size_t data_length) {
|
||||
@@ -704,27 +863,47 @@ void Session::VerifyRenewalRequestSignature(size_t data_length) {
|
||||
for (size_t i = 0; i < data.size(); i++) data[i] = i & 0xFF;
|
||||
OEMCryptoResult sts;
|
||||
size_t gen_signature_length = 0;
|
||||
|
||||
// TODO(b/135288420): Test core message functionality.
|
||||
// This function should be split into three versions, one for each core
|
||||
// message.
|
||||
size_t core_message_length = 0;
|
||||
sts = OEMCrypto_SignRenewalRequest(session_id(), data.data(), data.size(),
|
||||
nullptr, &core_message_length, nullptr,
|
||||
&gen_signature_length);
|
||||
sts = OEMCrypto_PrepAndSignRenewalRequest(session_id(), data.data(),
|
||||
data.size(), &core_message_length,
|
||||
nullptr, &gen_signature_length);
|
||||
ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts);
|
||||
const size_t hmac_signature_size = 32u;
|
||||
ASSERT_EQ(hmac_signature_size, gen_signature_length);
|
||||
ASSERT_EQ(HMAC_SHA256_SIGNATURE_SIZE, gen_signature_length);
|
||||
vector<uint8_t> gen_signature(gen_signature_length);
|
||||
sts = OEMCrypto_SignRenewalRequest(
|
||||
session_id(), data.data(), data.size(), nullptr, &core_message_length,
|
||||
sts = OEMCrypto_PrepAndSignRenewalRequest(
|
||||
session_id(), data.data(), data.size(), &core_message_length,
|
||||
gen_signature.data(), &gen_signature_length);
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
||||
if (global_features.api_version >= 16) {
|
||||
ASSERT_GT(data.size(), core_message_length);
|
||||
std::string core_message(reinterpret_cast<char*>(data.data()),
|
||||
core_message_length);
|
||||
VerifyCoreRenewalRequest(core_message);
|
||||
}
|
||||
std::vector<uint8_t> expected_signature;
|
||||
key_deriver_.ClientSignBuffer(data, &expected_signature);
|
||||
ASSERT_EQ(expected_signature, gen_signature);
|
||||
}
|
||||
|
||||
void Session::VerifyCoreLicenseRequest(const std::string& core_message) {
|
||||
oec_util::ODK_LicenseRequest core_license_request;
|
||||
EXPECT_TRUE(
|
||||
oec_util::ParseLicenseRequest(core_message, &core_license_request));
|
||||
EXPECT_EQ(global_features.api_version, core_license_request.api_version);
|
||||
if (nonce_) EXPECT_EQ(nonce_, core_license_request.nonce);
|
||||
EXPECT_EQ(session_id_, core_license_request.session_id);
|
||||
}
|
||||
|
||||
void Session::VerifyCoreRenewalRequest(const std::string& core_message) {
|
||||
oec_util::ODK_RenewalRequest core_renewal_request;
|
||||
EXPECT_TRUE(
|
||||
oec_util::ParseRenewalRequest(core_message, &core_renewal_request));
|
||||
EXPECT_EQ(global_features.api_version, core_renewal_request.api_version);
|
||||
// We do not check the nonce, because we don't know it if it comes from a
|
||||
// previous session.
|
||||
EXPECT_EQ(session_id_, core_renewal_request.session_id);
|
||||
}
|
||||
|
||||
void Session::FillKeyArray(const MessageData& data,
|
||||
OEMCrypto_KeyObject* key_array) {
|
||||
const uint8_t* data_ptr = reinterpret_cast<const uint8_t*>(&data);
|
||||
@@ -818,19 +997,20 @@ void Session::TestDecryptCTR(bool select_key_first,
|
||||
|
||||
// Describe the output
|
||||
vector<uint8_t> outputBuffer(256);
|
||||
OEMCrypto_DestBufferDesc destBuffer;
|
||||
destBuffer.type = OEMCrypto_BufferType_Clear;
|
||||
destBuffer.buffer.clear.address = outputBuffer.data();
|
||||
destBuffer.buffer.clear.max_length = outputBuffer.size();
|
||||
OEMCrypto_DestBufferDesc out_buffer_descriptor;
|
||||
out_buffer_descriptor.type = OEMCrypto_BufferType_Clear;
|
||||
out_buffer_descriptor.buffer.clear.address = outputBuffer.data();
|
||||
out_buffer_descriptor.buffer.clear.address_length = outputBuffer.size();
|
||||
OEMCrypto_CENCEncryptPatternDesc pattern;
|
||||
pattern.encrypt = 0;
|
||||
pattern.skip = 0;
|
||||
pattern.offset = 0;
|
||||
// Decrypt the data
|
||||
#if 0 // TODO(b/135285640): fix this.
|
||||
sts = OEMCrypto_DecryptCENC(
|
||||
session_id(), encryptedData.data(), encryptedData.size(), true,
|
||||
encryptionIv.data(), 0, &destBuffer, &pattern,
|
||||
encryptionIv.data(), 0, &out_buffer_descriptor, &pattern,
|
||||
OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample);
|
||||
#endif
|
||||
// We only have a few errors that we test are reported.
|
||||
if (expected_result == OEMCrypto_SUCCESS) { // No error.
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
||||
@@ -850,6 +1030,13 @@ void Session::TestDecryptResult(OEMCryptoResult expected_result,
|
||||
global_features.api_version >= 9) {
|
||||
// Report stale keys, required in v9 and beyond.
|
||||
ASSERT_EQ(OEMCrypto_ERROR_KEY_EXPIRED, actual_result);
|
||||
} else if (expected_result == OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION &&
|
||||
global_features.api_version >= 16) {
|
||||
// OEMCrypto is allowed to report either this warning or
|
||||
// OEMCrypto_ERROR_INSUFFICIENT_HDCP depending on if it can disable
|
||||
// restricted displays.
|
||||
ASSERT_TRUE(actual_result == OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION ||
|
||||
actual_result == OEMCrypto_ERROR_INSUFFICIENT_HDCP);
|
||||
} else if (expected_result == OEMCrypto_ERROR_INSUFFICIENT_HDCP) {
|
||||
// Report HDCP errors.
|
||||
ASSERT_EQ(OEMCrypto_ERROR_INSUFFICIENT_HDCP, actual_result);
|
||||
@@ -944,82 +1131,6 @@ void Session::LoadOEMCert(bool verify_cert) {
|
||||
}
|
||||
}
|
||||
|
||||
void Session::MakeRSACertificate(struct RSAPrivateKeyMessage* encrypted,
|
||||
size_t message_size,
|
||||
std::vector<uint8_t>* signature,
|
||||
uint32_t allowed_schemes,
|
||||
const vector<uint8_t>& rsa_key,
|
||||
const Encryptor* encryptor) {
|
||||
if (encryptor == nullptr) encryptor = &key_deriver_;
|
||||
struct RSAPrivateKeyMessage message;
|
||||
if (allowed_schemes != kSign_RSASSA_PSS) {
|
||||
uint32_t algorithm_n = htonl(allowed_schemes);
|
||||
memcpy(message.rsa_key, "SIGN", 4);
|
||||
memcpy(message.rsa_key + 4, &algorithm_n, 4);
|
||||
memcpy(message.rsa_key + 8, rsa_key.data(), rsa_key.size());
|
||||
message.rsa_key_length = 8 + rsa_key.size();
|
||||
} else {
|
||||
memcpy(message.rsa_key, rsa_key.data(), rsa_key.size());
|
||||
message.rsa_key_length = rsa_key.size();
|
||||
}
|
||||
GenerateNonce();
|
||||
message.nonce = nonce_;
|
||||
encryptor->PadAndEncryptProvisioningMessage(&message, encrypted);
|
||||
key_deriver_.ServerSignBuffer(reinterpret_cast<const uint8_t*>(encrypted),
|
||||
message_size, signature);
|
||||
}
|
||||
|
||||
void Session::RewrapRSAKey(const struct RSAPrivateKeyMessage& encrypted,
|
||||
size_t message_size,
|
||||
const std::vector<uint8_t>& signature,
|
||||
vector<uint8_t>* wrapped_key, bool force) {
|
||||
size_t wrapped_key_length = 0;
|
||||
const uint8_t* message_ptr = reinterpret_cast<const uint8_t*>(&encrypted);
|
||||
ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER,
|
||||
OEMCrypto_RewrapDeviceRSAKey(
|
||||
session_id(), message_ptr, message_size, signature.data(),
|
||||
signature.size(), &encrypted.nonce, encrypted.rsa_key,
|
||||
encrypted.rsa_key_length, encrypted.rsa_key_iv, nullptr,
|
||||
&wrapped_key_length));
|
||||
wrapped_key->clear();
|
||||
wrapped_key->assign(wrapped_key_length, 0);
|
||||
OEMCryptoResult sts = OEMCrypto_RewrapDeviceRSAKey(
|
||||
session_id(), message_ptr, message_size, signature.data(),
|
||||
signature.size(), &encrypted.nonce, encrypted.rsa_key,
|
||||
encrypted.rsa_key_length, encrypted.rsa_key_iv, &(wrapped_key->front()),
|
||||
&wrapped_key_length);
|
||||
if (force) {
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
||||
}
|
||||
if (OEMCrypto_SUCCESS != sts) {
|
||||
wrapped_key->clear();
|
||||
}
|
||||
}
|
||||
|
||||
void Session::RewrapRSAKey30(const struct RSAPrivateKeyMessage& encrypted,
|
||||
const std::vector<uint8_t>& encrypted_message_key,
|
||||
vector<uint8_t>* wrapped_key, bool force) {
|
||||
size_t wrapped_key_length = 0;
|
||||
ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER,
|
||||
OEMCrypto_RewrapDeviceRSAKey30(
|
||||
session_id(), &nonce_, encrypted_message_key.data(),
|
||||
encrypted_message_key.size(), encrypted.rsa_key,
|
||||
encrypted.rsa_key_length, encrypted.rsa_key_iv, nullptr,
|
||||
&wrapped_key_length));
|
||||
wrapped_key->clear();
|
||||
wrapped_key->assign(wrapped_key_length, 0);
|
||||
OEMCryptoResult sts = OEMCrypto_RewrapDeviceRSAKey30(
|
||||
session_id(), &nonce_, encrypted_message_key.data(),
|
||||
encrypted_message_key.size(), encrypted.rsa_key, encrypted.rsa_key_length,
|
||||
encrypted.rsa_key_iv, &(wrapped_key->front()), &wrapped_key_length);
|
||||
if (force) {
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
||||
}
|
||||
if (OEMCrypto_SUCCESS != sts) {
|
||||
wrapped_key->clear();
|
||||
}
|
||||
}
|
||||
|
||||
void Session::PreparePublicKey(const uint8_t* rsa_key, size_t rsa_key_length) {
|
||||
if (rsa_key == nullptr) {
|
||||
rsa_key = kTestRSAPKCS8PrivateKeyInfo2_2048;
|
||||
@@ -1164,7 +1275,6 @@ void Session::InstallRSASessionTestKey(const vector<uint8_t>& wrapped_rsa_key) {
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS,
|
||||
OEMCrypto_LoadDeviceRSAKey(session_id(), wrapped_rsa_key.data(),
|
||||
wrapped_rsa_key.size()));
|
||||
GenerateDerivedKeysFromSessionKey();
|
||||
}
|
||||
|
||||
void Session::CreateNewUsageEntry(OEMCryptoResult* status) {
|
||||
@@ -1258,7 +1368,8 @@ void Session::GenerateReport(const std::string& pst,
|
||||
EXPECT_EQ(pst.length(), pst_report().pst_length());
|
||||
EXPECT_EQ(0, memcmp(pst.c_str(), pst_report().pst(), pst.length()));
|
||||
// Also, we the session to be able to sign the release message with the
|
||||
// correct mac keys from the usage table entry.
|
||||
// correct mac keys from the usage table entry. Note that we sign both the PST
|
||||
// report above with ClientSignPstReport, and we sign the renewal request.
|
||||
ASSERT_NO_FATAL_FAILURE(VerifyRenewalRequestSignature());
|
||||
}
|
||||
|
||||
@@ -1268,7 +1379,7 @@ void Session::VerifyPST(const Test_PST_Report& expected) {
|
||||
char* pst_ptr = reinterpret_cast<char *>(computed.pst());
|
||||
std::string computed_pst(pst_ptr, pst_ptr + computed.pst_length());
|
||||
ASSERT_EQ(expected.pst, computed_pst);
|
||||
time_t now = time(nullptr);
|
||||
int64_t now = wvcdm::Clock().GetCurrentTime();
|
||||
int64_t age = now - expected.time_created; // How old is this report.
|
||||
EXPECT_NEAR(expected.seconds_since_license_received + age,
|
||||
computed.seconds_since_license_received(),
|
||||
@@ -1291,7 +1402,7 @@ void Session::VerifyPST(const Test_PST_Report& expected) {
|
||||
// This might adjust t to be "seconds since now". If t is small, we assume it
|
||||
// is "seconds since now", but if the value of t is large, assume it is
|
||||
// "absolute time" and convert to "seconds since now".
|
||||
static int64_t MaybeAdjustTime(int64_t t, time_t now) {
|
||||
static int64_t MaybeAdjustTime(int64_t t, int64_t now) {
|
||||
int64_t k10Minutes = 60 * 10; // in seconds.
|
||||
if (t > k10Minutes) return now - t;
|
||||
return t;
|
||||
@@ -1301,7 +1412,7 @@ void Session::VerifyReport(Test_PST_Report expected,
|
||||
int64_t time_license_received,
|
||||
int64_t time_first_decrypt,
|
||||
int64_t time_last_decrypt) {
|
||||
time_t now = time(nullptr);
|
||||
const int64_t now = wvcdm::Clock().GetCurrentTime();
|
||||
expected.seconds_since_license_received =
|
||||
MaybeAdjustTime(time_license_received, now);
|
||||
expected.seconds_since_first_decrypt =
|
||||
|
||||
@@ -7,13 +7,16 @@
|
||||
//
|
||||
// OEMCrypto unit tests
|
||||
//
|
||||
#include <gtest/gtest.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <time.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "odk.h"
|
||||
#include "oec_device_features.h"
|
||||
#include "oec_key_deriver.h"
|
||||
#include "oec_util.h"
|
||||
#include "oemcrypto_types.h"
|
||||
#include "pst_report.h"
|
||||
|
||||
@@ -30,21 +33,21 @@ void PrintTo(const vector<uint8_t>& value, ostream* os);
|
||||
namespace wvoec {
|
||||
|
||||
// Make sure this is larger than kMaxKeysPerSession, in oemcrypto_test.cpp
|
||||
const size_t kMaxNumKeys = 20;
|
||||
constexpr size_t kMaxNumKeys = 35;
|
||||
|
||||
namespace {
|
||||
#if defined(TEST_SPEED_MULTIPLIER) // Can slow test time limits when
|
||||
// debugging is slowing everything.
|
||||
const int kSpeedMultiplier = TEST_SPEED_MULTIPLIER;
|
||||
constexpr int kSpeedMultiplier = TEST_SPEED_MULTIPLIER;
|
||||
#else
|
||||
const int kSpeedMultiplier = 1;
|
||||
constexpr int kSpeedMultiplier = 1;
|
||||
#endif
|
||||
const int kShortSleep = 1 * kSpeedMultiplier;
|
||||
const int kLongSleep = 2 * kSpeedMultiplier;
|
||||
const uint32_t kDuration = 2 * kSpeedMultiplier;
|
||||
const uint32_t kLongDuration = 5 * kSpeedMultiplier;
|
||||
const int32_t kTimeTolerance = 3 * kSpeedMultiplier;
|
||||
const time_t kUsageTableTimeTolerance = 10 * kSpeedMultiplier;
|
||||
constexpr int kShortSleep = 1 * kSpeedMultiplier;
|
||||
constexpr int kLongSleep = 2 * kSpeedMultiplier;
|
||||
constexpr uint32_t kDuration = 2 * kSpeedMultiplier;
|
||||
constexpr uint32_t kLongDuration = 5 * kSpeedMultiplier;
|
||||
constexpr int32_t kTimeTolerance = 3 * kSpeedMultiplier;
|
||||
constexpr int64_t kUsageTableTimeTolerance = 10 * kSpeedMultiplier;
|
||||
} // namespace
|
||||
|
||||
typedef struct {
|
||||
@@ -56,13 +59,14 @@ typedef struct {
|
||||
|
||||
// Note: The API does not specify a maximum key id length. We specify a
|
||||
// maximum just for these tests, so that we have a fixed message size.
|
||||
const size_t kTestKeyIdMaxLength = 16;
|
||||
constexpr size_t kTestKeyIdMaxLength = 16;
|
||||
|
||||
// Most content will use a key id that is 16 bytes long.
|
||||
const int kDefaultKeyIdLength = 16;
|
||||
constexpr int kDefaultKeyIdLength = 16;
|
||||
|
||||
const size_t kMaxPSTLength = 255; // In specification.
|
||||
const size_t kMaxMessageSize = 8 * 1024; // In specification.
|
||||
constexpr size_t kMaxPSTLength = 255; // In specification.
|
||||
constexpr size_t kMaxMessageSize = 8 * 1024; // In specification.
|
||||
constexpr size_t kMaxCoreMessage = 20 * kMaxNumKeys + 150; // Rough estimate.
|
||||
|
||||
typedef struct {
|
||||
uint8_t key_id[kTestKeyIdMaxLength];
|
||||
@@ -79,6 +83,7 @@ typedef struct {
|
||||
|
||||
// This structure will be signed to simulate a message from the server.
|
||||
struct MessageData {
|
||||
uint8_t core_message[kMaxCoreMessage];
|
||||
MessageKeyData keys[kMaxNumKeys];
|
||||
uint8_t mac_key_iv[KEY_IV_SIZE];
|
||||
uint8_t padding[KEY_IV_SIZE];
|
||||
@@ -88,15 +93,14 @@ struct MessageData {
|
||||
|
||||
struct Test_PST_Report {
|
||||
Test_PST_Report(const std::string& pst_in,
|
||||
OEMCrypto_Usage_Entry_Status status_in)
|
||||
: status(status_in), pst(pst_in), time_created(time(nullptr)) {}
|
||||
OEMCrypto_Usage_Entry_Status status_in);
|
||||
|
||||
OEMCrypto_Usage_Entry_Status status;
|
||||
int64_t seconds_since_license_received;
|
||||
int64_t seconds_since_first_decrypt;
|
||||
int64_t seconds_since_last_decrypt;
|
||||
std::string pst;
|
||||
time_t time_created;
|
||||
int64_t time_created;
|
||||
};
|
||||
|
||||
struct EntitledContentKeyData {
|
||||
@@ -128,6 +132,158 @@ OEMCrypto_Substring GetSubstring(const std::string& message = "",
|
||||
const std::string& field = "",
|
||||
bool set_zero = false);
|
||||
|
||||
class Session;
|
||||
// The signature of the OEMCrypto function to prepare and sign a request.
|
||||
typedef OEMCryptoResult (*PrepAndSignRequest_t)(
|
||||
OEMCrypto_SESSION session, uint8_t* message, size_t message_length,
|
||||
size_t* core_message_length, uint8_t* signature, size_t* signature_length);
|
||||
|
||||
// A RoundTrip helps generate and verify a request message, helps generate the
|
||||
// corresponding response, and then helps verify loading the response.
|
||||
template <class CoreRequest, PrepAndSignRequest_t PrepAndSignRequest,
|
||||
class CoreResponse, class ResponseData>
|
||||
class RoundTrip {
|
||||
public:
|
||||
RoundTrip() = delete;
|
||||
RoundTrip(Session* session)
|
||||
: session_(session),
|
||||
message_size_(sizeof(ResponseData) + kMaxCoreMessage),
|
||||
nonce_(0){};
|
||||
virtual ~RoundTrip() {}
|
||||
|
||||
// Have OEMCrypto sign a request message and then verify the signature and the
|
||||
// core message.
|
||||
virtual void SignAndVerifyRequest();
|
||||
// Create a default |core_response| and |response_data|.
|
||||
virtual void CreateDefaultResponse() = 0;
|
||||
// Copy fields from |reponse_data| to |padded_response_data|, encrypting those
|
||||
// that should be encrypted. Serialize the core message. Then sign the
|
||||
// response.
|
||||
virtual void EncryptAndSignResponse() = 0;
|
||||
// Attempt to load the response and return the error. Short buffer errors are
|
||||
// handled by LoadResponse, not the caller. All other errors should be
|
||||
// handled by the caller.
|
||||
virtual OEMCryptoResult LoadResponse() = 0;
|
||||
|
||||
// Accessors are all read/write because tests modify default values.
|
||||
Session* session() { return session_; }
|
||||
CoreRequest& core_request() { return core_request_; }
|
||||
CoreResponse& core_response() { return core_response_; }
|
||||
ResponseData& response_data() { return response_data_; }
|
||||
ResponseData& encrypted_response_data() { return encrypted_response_data_; }
|
||||
std::vector<uint8_t>& encrypted_response_buffer() {
|
||||
return encrypted_response_;
|
||||
}
|
||||
|
||||
// Set the size of the buffer used the encrypted license.
|
||||
// Must be between sizeof(MessageData) and kMaxMessageSize.
|
||||
void set_message_size(size_t size) {
|
||||
message_size_ = size;
|
||||
ASSERT_GE(message_size_, sizeof(ResponseData) + kMaxCoreMessage);
|
||||
ASSERT_LE(message_size_, kMaxMessageSize);
|
||||
}
|
||||
// The size of the encrypted message.
|
||||
size_t message_size() { return message_size_; }
|
||||
std::vector<uint8_t>& response_signature() { return response_signature_; }
|
||||
|
||||
protected:
|
||||
// ----------------------------------------------------------------------
|
||||
// Specialized functionality for each message type.
|
||||
|
||||
// Verify the signature of the request.
|
||||
virtual void VerifyRequestSignature(
|
||||
const vector<uint8_t>& data,
|
||||
const vector<uint8_t>& generated_signature) = 0;
|
||||
// Verify the values of the core response.
|
||||
virtual void FillAndVerifyCoreRequest(
|
||||
const std::string& core_message_string) = 0;
|
||||
// Find the given pointer in the response_data_.
|
||||
virtual OEMCrypto_Substring FindSubstring(const void* pointer, size_t length);
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Member variables.
|
||||
Session* session_;
|
||||
CoreRequest core_request_;
|
||||
CoreResponse core_response_;
|
||||
ResponseData response_data_, encrypted_response_data_;
|
||||
size_t message_size_; // How much of the padded message to use.
|
||||
std::vector<uint8_t> response_signature_;
|
||||
uint32_t nonce_;
|
||||
std::string serialized_core_message_;
|
||||
std::vector<uint8_t> encrypted_response_;
|
||||
};
|
||||
|
||||
class ProvisioningRoundTrip
|
||||
: public RoundTrip<oec_util::ODK_ProvisioningRequest,
|
||||
OEMCrypto_PrepAndSignProvisioningRequest,
|
||||
ODK_ParsedProvisioning, RSAPrivateKeyMessage> {
|
||||
public:
|
||||
ProvisioningRoundTrip(Session* session,
|
||||
const std::vector<uint8_t>& encoded_rsa_key)
|
||||
: RoundTrip(session),
|
||||
allowed_schemes_(kSign_RSASSA_PSS),
|
||||
encoded_rsa_key_(encoded_rsa_key) {}
|
||||
// Prepare the session for signing the request.
|
||||
virtual void PrepareSession(const wvoec::WidevineKeybox& keybox);
|
||||
void CreateDefaultResponse() override;
|
||||
void EncryptAndSignResponse() override;
|
||||
OEMCryptoResult LoadResponse() override;
|
||||
void VerifyLoadFailed();
|
||||
const std::vector<uint8_t>& encoded_rsa_key() { return encoded_rsa_key_; }
|
||||
const std::vector<uint8_t>& wrapped_rsa_key() { return wrapped_rsa_key_; }
|
||||
void set_allowed_schemes(uint32_t allowed_schemes) {
|
||||
allowed_schemes_ = allowed_schemes;
|
||||
}
|
||||
|
||||
protected:
|
||||
void VerifyRequestSignature(
|
||||
const vector<uint8_t>& data,
|
||||
const vector<uint8_t>& generated_signature) override;
|
||||
// Verify the values of the core response.
|
||||
virtual void FillAndVerifyCoreRequest(
|
||||
const std::string& core_message_string) override;
|
||||
uint32_t allowed_schemes_;
|
||||
Encryptor encryptor_;
|
||||
// The message key used for Prov 3.0.
|
||||
std::vector<uint8_t> message_key_;
|
||||
std::vector<uint8_t> encrypted_message_key_;
|
||||
std::vector<uint8_t> encoded_rsa_key_;
|
||||
std::vector<uint8_t> wrapped_rsa_key_;
|
||||
};
|
||||
|
||||
class LicenseRoundTrip : public RoundTrip<oec_util::ODK_LicenseRequest,
|
||||
OEMCrypto_PrepAndSignLicenseRequest,
|
||||
ODK_ParsedLicense, MessageData> {
|
||||
public:
|
||||
LicenseRoundTrip(Session* session) : RoundTrip(session) {}
|
||||
|
||||
protected:
|
||||
void VerifyRequestSignature(
|
||||
const vector<uint8_t>& data,
|
||||
const vector<uint8_t>& generated_signature) override;
|
||||
// Verify the values of the core response.
|
||||
virtual void FillAndVerifyCoreRequest(
|
||||
const std::string& core_message_string) override;
|
||||
};
|
||||
|
||||
class RenewalRoundTrip
|
||||
: public RoundTrip<oec_util::ODK_RenewalRequest,
|
||||
OEMCrypto_PrepAndSignRenewalRequest,
|
||||
oec_util::ODK_RenewalRequest, // Response has same
|
||||
// information as request.
|
||||
MessageData> {
|
||||
public:
|
||||
RenewalRoundTrip(Session* session) : RoundTrip(session) {}
|
||||
|
||||
protected:
|
||||
void VerifyRequestSignature(
|
||||
const vector<uint8_t>& data,
|
||||
const vector<uint8_t>& generated_signature) override;
|
||||
// Verify the values of the core response.
|
||||
virtual void FillAndVerifyCoreRequest(
|
||||
const std::string& core_message_string) override;
|
||||
};
|
||||
|
||||
class Session {
|
||||
public:
|
||||
Session();
|
||||
@@ -218,15 +374,16 @@ class Session {
|
||||
// the server mac keys. It then fills out the key_array_ so that pointers in
|
||||
// that array point to the locations in the encrypted message.
|
||||
void EncryptAndSign();
|
||||
// This checks the signature generated by OEMCrypto_SignProvisioningRequest
|
||||
// This checks the signature generated by OEMCrypto_PrepAndSignLicenseRequest
|
||||
// against that generaged by ClientSignBuffer.
|
||||
void VerifyProvisioningRequestSignature(size_t data_length = 400);
|
||||
// This checks the signature generated by OEMCrypto_SignLicenseRequest against
|
||||
// that generaged by ClientSignBuffer.
|
||||
void VerifyLicenseRequestSignature(size_t data_length = 400);
|
||||
// This checks the signature generated by OEMCrypto_SignRenewalRequest against
|
||||
// that generaged by ClientSignBuffer.
|
||||
// This checks the signature generated by OEMCrypto_PrepAndSignRenewalRequest
|
||||
// against that generaged by ClientSignBuffer.
|
||||
void VerifyRenewalRequestSignature(size_t data_length = 400);
|
||||
// Verify the core message matches data from this session.
|
||||
void VerifyCoreLicenseRequest(const std::string& core_message);
|
||||
// Verify the core message matches data from this session.
|
||||
void VerifyCoreRenewalRequest(const std::string& core_message);
|
||||
// Set the pointers in key_array[*] to point values inside data. This is
|
||||
// needed to satisfy range checks in OEMCrypto_LoadKeys.
|
||||
void FillKeyArray(const MessageData& data, OEMCrypto_KeyObject* key_array);
|
||||
@@ -251,14 +408,6 @@ class Session {
|
||||
// Calls OEMCrypto_GetOEMPublicCertificate and OEMCrypto_LoadOEMPrivateKey and
|
||||
// loads the OEM cert's public rsa key into public_rsa_.
|
||||
void LoadOEMCert(bool verify_cert = false);
|
||||
// Creates RSAPrivateKeyMessage for the specified rsa_key, encrypts it with
|
||||
// the specified encryption key, and then signs it with the server's mac key.
|
||||
// If encryption_key is null, use the session's enc_key_.
|
||||
void MakeRSACertificate(struct RSAPrivateKeyMessage* encrypted,
|
||||
size_t message_size, std::vector<uint8_t>* signature,
|
||||
uint32_t allowed_schemes,
|
||||
const vector<uint8_t>& rsa_key,
|
||||
const Encryptor* encryptor = nullptr);
|
||||
// Calls OEMCrypto_RewrapDeviceRSAKey with the given provisioning response
|
||||
// message. If force is true, we assert that the key loads successfully.
|
||||
void RewrapRSAKey(const struct RSAPrivateKeyMessage& encrypted,
|
||||
@@ -318,7 +467,7 @@ class Session {
|
||||
}
|
||||
// Generates a usage report for the specified pst. If there is success,
|
||||
// the report's signature is verified, and several fields are given sanity
|
||||
// checks. If other is not null, then the mac keys are copied from other in
|
||||
// checks. If |other| is not null, then the mac keys are copied from other in
|
||||
// order to verify signatures.
|
||||
void GenerateReport(const std::string& pst,
|
||||
OEMCryptoResult expected_result = OEMCrypto_SUCCESS,
|
||||
@@ -400,6 +549,7 @@ class Session {
|
||||
|
||||
// Pointer to buffer holding |encrypted_entitled_message_|
|
||||
const uint8_t* encrypted_entitled_message_ptr();
|
||||
const KeyDeriver& key_deriver() const { return key_deriver_; }
|
||||
|
||||
private:
|
||||
bool open_;
|
||||
|
||||
@@ -20,68 +20,16 @@ const uint8_t* find(const vector<uint8_t>& message,
|
||||
return &(*pos);
|
||||
}
|
||||
|
||||
// This creates a wrapped RSA key for devices that have the test keybox
|
||||
// installed. If force is true, we assert that the key loads successfully.
|
||||
void SessionUtil::CreateWrappedRSAKeyFromKeybox(uint32_t allowed_schemes,
|
||||
bool force) {
|
||||
// This creates a wrapped RSA key.
|
||||
void SessionUtil::CreateWrappedRSAKey() {
|
||||
Session s;
|
||||
ASSERT_NO_FATAL_FAILURE(s.open());
|
||||
ASSERT_NO_FATAL_FAILURE(s.GenerateDerivedKeysFromKeybox(keybox_));
|
||||
// Provisioning request would be signed by the client and verified by the
|
||||
// server.
|
||||
ASSERT_NO_FATAL_FAILURE(s.VerifyProvisioningRequestSignature());
|
||||
struct RSAPrivateKeyMessage encrypted;
|
||||
std::vector<uint8_t> signature;
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
s.MakeRSACertificate(&encrypted, sizeof(encrypted),
|
||||
&signature, allowed_schemes,
|
||||
encoded_rsa_key_));
|
||||
ASSERT_NO_FATAL_FAILURE(s.RewrapRSAKey(
|
||||
encrypted, sizeof(encrypted), signature, &wrapped_rsa_key_, force));
|
||||
// Verify that the clear key is not contained in the wrapped key.
|
||||
// It should be encrypted.
|
||||
ASSERT_EQ(nullptr, find(wrapped_rsa_key_, encoded_rsa_key_));
|
||||
}
|
||||
|
||||
// This creates a wrapped RSA key for devices using provisioning 3.0. If force
|
||||
// is true, we assert that the key loads successfully.
|
||||
void SessionUtil::CreateWrappedRSAKeyFromOEMCert(
|
||||
uint32_t allowed_schemes, bool force) {
|
||||
Session s;
|
||||
ASSERT_NO_FATAL_FAILURE(s.open());
|
||||
ASSERT_NO_FATAL_FAILURE(s.LoadOEMCert());
|
||||
struct RSAPrivateKeyMessage encrypted;
|
||||
std::vector<uint8_t> signature;
|
||||
std::vector<uint8_t> message_key;
|
||||
std::vector<uint8_t> encrypted_message_key;
|
||||
s.GenerateRSASessionKey(&message_key, &encrypted_message_key);
|
||||
Encryptor encryptor(message_key);
|
||||
ASSERT_NO_FATAL_FAILURE(s.MakeRSACertificate(&encrypted, sizeof(encrypted),
|
||||
&signature, allowed_schemes,
|
||||
encoded_rsa_key_, &encryptor));
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
s.RewrapRSAKey30(encrypted, encrypted_message_key,
|
||||
&wrapped_rsa_key_, force));
|
||||
// Verify that the clear key is not contained in the wrapped key.
|
||||
// It should be encrypted.
|
||||
ASSERT_EQ(nullptr, find(wrapped_rsa_key_, encoded_rsa_key_));
|
||||
}
|
||||
|
||||
// If force is true, we assert that the key loads successfully.
|
||||
void SessionUtil::CreateWrappedRSAKey(uint32_t allowed_schemes,
|
||||
bool force) {
|
||||
switch (global_features.provisioning_method) {
|
||||
case OEMCrypto_OEMCertificate:
|
||||
CreateWrappedRSAKeyFromOEMCert(allowed_schemes, force);
|
||||
break;
|
||||
case OEMCrypto_Keybox:
|
||||
CreateWrappedRSAKeyFromKeybox(allowed_schemes, force);
|
||||
break;
|
||||
default:
|
||||
FAIL() << "Cannot generate wrapped RSA key if provision method = "
|
||||
<< wvoec::ProvisioningMethodName(
|
||||
global_features.provisioning_method);
|
||||
}
|
||||
ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_);
|
||||
provisioning_messages.PrepareSession(keybox_);
|
||||
ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest());
|
||||
ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse());
|
||||
ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse());
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, provisioning_messages.LoadResponse());
|
||||
wrapped_rsa_key_ = provisioning_messages.wrapped_rsa_key();
|
||||
}
|
||||
|
||||
void SessionUtil::InstallKeybox(const wvoec::WidevineKeybox& keybox,
|
||||
@@ -104,10 +52,10 @@ void SessionUtil::EnsureTestKeys() {
|
||||
switch (global_features.derive_key_method) {
|
||||
case DeviceFeatures::LOAD_TEST_KEYBOX:
|
||||
keybox_ = kTestKeybox;
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS,
|
||||
OEMCrypto_LoadTestKeybox(
|
||||
reinterpret_cast<const uint8_t*>(&keybox_),
|
||||
sizeof(keybox_)));
|
||||
ASSERT_EQ(
|
||||
OEMCrypto_SUCCESS,
|
||||
OEMCrypto_LoadTestKeybox(reinterpret_cast<const uint8_t*>(&keybox_),
|
||||
sizeof(keybox_)));
|
||||
break;
|
||||
case DeviceFeatures::LOAD_TEST_RSA_KEY:
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_LoadTestRSAKey());
|
||||
@@ -126,26 +74,18 @@ void SessionUtil::EnsureTestKeys() {
|
||||
|
||||
// This makes sure that the derived keys (encryption key and two mac keys)
|
||||
// are installed in OEMCrypto and in the test session.
|
||||
void SessionUtil::InstallTestSessionKeys(Session* s) {
|
||||
if (global_features.uses_certificate) {
|
||||
if (global_features.loads_certificate) {
|
||||
if (wrapped_rsa_key_.size() == 0) {
|
||||
// If we don't have a wrapped key yet, create one.
|
||||
// This wrapped key will be shared by all sessions in the test.
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
CreateWrappedRSAKey(kSign_RSASSA_PSS, true));
|
||||
}
|
||||
// Load the wrapped rsa test key.
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
s->InstallRSASessionTestKey(wrapped_rsa_key_));
|
||||
void SessionUtil::InstallTestRSAKey(Session* s) {
|
||||
ASSERT_TRUE(global_features.uses_certificate);
|
||||
if (global_features.loads_certificate) {
|
||||
if (wrapped_rsa_key_.size() == 0) {
|
||||
// If we don't have a wrapped key yet, create one.
|
||||
// This wrapped key will be shared by all sessions in the test.
|
||||
ASSERT_NO_FATAL_FAILURE(CreateWrappedRSAKey());
|
||||
}
|
||||
// Test RSA key should be loaded.
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
s->GenerateDerivedKeysFromSessionKey());
|
||||
} else { // Just uses keybox. Test keybox should already be installed.
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
s->GenerateDerivedKeysFromKeybox(keybox_));
|
||||
// Load the wrapped rsa test key.
|
||||
ASSERT_NO_FATAL_FAILURE(s->InstallRSASessionTestKey(wrapped_rsa_key_));
|
||||
}
|
||||
// Test RSA key should be loaded.
|
||||
ASSERT_NO_FATAL_FAILURE(s->GenerateDerivedKeysFromSessionKey());
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace wvoec
|
||||
|
||||
@@ -12,33 +12,27 @@ namespace wvoec {
|
||||
|
||||
class SessionUtil {
|
||||
public:
|
||||
SessionUtil()
|
||||
: encoded_rsa_key_(kTestRSAPKCS8PrivateKeyInfo2_2048,
|
||||
kTestRSAPKCS8PrivateKeyInfo2_2048 +
|
||||
sizeof(kTestRSAPKCS8PrivateKeyInfo2_2048)) {}
|
||||
SessionUtil()
|
||||
: encoded_rsa_key_(kTestRSAPKCS8PrivateKeyInfo2_2048,
|
||||
kTestRSAPKCS8PrivateKeyInfo2_2048 +
|
||||
sizeof(kTestRSAPKCS8PrivateKeyInfo2_2048)) {}
|
||||
|
||||
// If force is true, we assert that the key loads successfully.
|
||||
void CreateWrappedRSAKeyFromKeybox(uint32_t allowed_schemes, bool force);
|
||||
// Create a new wrapped DRM Certificate.
|
||||
void CreateWrappedRSAKey();
|
||||
|
||||
// If force is true, we assert that the key loads successfully.
|
||||
void CreateWrappedRSAKeyFromOEMCert(uint32_t allowed_schemes, bool force);
|
||||
// This is used to force installation of a keybox. This overwrites the
|
||||
// production keybox -- it does NOT use OEMCrypto_LoadTestKeybox.
|
||||
void InstallKeybox(const wvoec::WidevineKeybox& keybox, bool good);
|
||||
|
||||
// If force is true, we assert that the key loads successfully.
|
||||
void CreateWrappedRSAKey(uint32_t allowed_schemes, bool force);
|
||||
// This loads the test keybox or the test RSA key, using LoadTestKeybox or
|
||||
// LoadTestRSAKey as needed.
|
||||
void EnsureTestKeys();
|
||||
|
||||
// This is used to force installation of a keybox. This overwrites the
|
||||
// production keybox -- it does NOT use OEMCrypto_LoadTestKeybox.
|
||||
void InstallKeybox(const wvoec::WidevineKeybox& keybox, bool good);
|
||||
void InstallTestRSAKey(Session* s);
|
||||
|
||||
// This loads the test keybox or the test RSA key, using LoadTestKeybox or
|
||||
// LoadTestRSAKey as needed.
|
||||
void EnsureTestKeys();
|
||||
|
||||
void InstallTestSessionKeys(Session* s);
|
||||
|
||||
std::vector<uint8_t> encoded_rsa_key_;
|
||||
std::vector<uint8_t> wrapped_rsa_key_;
|
||||
wvoec::WidevineKeybox keybox_;
|
||||
std::vector<uint8_t> encoded_rsa_key_;
|
||||
std::vector<uint8_t> wrapped_rsa_key_;
|
||||
wvoec::WidevineKeybox keybox_;
|
||||
};
|
||||
|
||||
} // namespace wvoec
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -60,23 +60,9 @@ TEST_F(OEMCryptoAndroidLMPTest, ValidKeyboxTest) {
|
||||
}
|
||||
|
||||
TEST_F(OEMCryptoAndroidLMPTest, RewrapDeviceRSAKeyImplemented) {
|
||||
if (OEMCrypto_Keybox == OEMCrypto_GetProvisioningMethod()) {
|
||||
ASSERT_NE(
|
||||
OEMCrypto_ERROR_NOT_IMPLEMENTED,
|
||||
OEMCrypto_RewrapDeviceRSAKey(0, nullptr, 0, nullptr, 0, nullptr,
|
||||
nullptr, 0, nullptr, nullptr, nullptr));
|
||||
} else {
|
||||
ASSERT_NE(OEMCrypto_ERROR_NOT_IMPLEMENTED,
|
||||
OEMCrypto_RewrapDeviceRSAKey30(0, nullptr, nullptr, 0, nullptr, 0,
|
||||
nullptr, nullptr, nullptr));
|
||||
}
|
||||
}
|
||||
|
||||
// This verifies that the device can load a DRM Certificate.
|
||||
TEST_F(OEMCryptoAndroidLMPTest, RSASignatureImplemented) {
|
||||
ASSERT_NE(OEMCrypto_ERROR_NOT_IMPLEMENTED,
|
||||
OEMCrypto_GenerateRSASignature(0, nullptr, 0, nullptr, nullptr,
|
||||
kSign_RSASSA_PSS));
|
||||
OEMCrypto_LoadProvisioning(0, nullptr, 0, 0, nullptr, 0, nullptr,
|
||||
nullptr));
|
||||
}
|
||||
|
||||
// The Generic Crypto API functions are required for Android.
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "OEMCryptoCENC.h"
|
||||
#include "log.h"
|
||||
#include "oec_device_features.h"
|
||||
#include "test_sleep.h"
|
||||
|
||||
static void acknowledge_cast() {
|
||||
std::cout
|
||||
@@ -36,6 +37,9 @@ int main(int argc, char** argv) {
|
||||
if (arg == "--no_filter") {
|
||||
filter_tests = false;
|
||||
}
|
||||
if (arg == "--fake_sleep") {
|
||||
wvcdm::TestSleep::set_real_sleep(false);
|
||||
}
|
||||
}
|
||||
wvcdm::g_cutoff = static_cast<wvcdm::LogPriority>(verbosity);
|
||||
wvoec::global_features.Initialize(is_cast_receiver, force_load_test_keybox);
|
||||
|
||||
@@ -15,9 +15,12 @@
|
||||
],
|
||||
'include_dirs': [
|
||||
'<(util_dir)/include',
|
||||
'<(util_dir)/test',
|
||||
'<(oemcrypto_dir)/include',
|
||||
'<(oemcrypto_dir)/ref/src',
|
||||
'<(oemcrypto_dir)/test',
|
||||
'<(oemcrypto_dir)/odk/include',
|
||||
'<(oemcrypto_dir)/util/include',
|
||||
],
|
||||
'defines': [
|
||||
'OEMCRYPTO_TESTS',
|
||||
|
||||
124
util/test/base64_test.cpp
Normal file
124
util/test/base64_test.cpp
Normal file
@@ -0,0 +1,124 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "log.h"
|
||||
#include "string_conversions.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
namespace {
|
||||
|
||||
// Test vectors as suggested by http://tools.ietf.org/html/rfc4648#section-10
|
||||
const std::string kNullString("");
|
||||
const std::string kf("f");
|
||||
const std::string kfo("fo");
|
||||
const std::string kfoo("foo");
|
||||
const std::string kfoob("foob");
|
||||
const std::string kfooba("fooba");
|
||||
const std::string kfoobar("foobar");
|
||||
const std::string kfB64("Zg==");
|
||||
const std::string kfoB64("Zm8=");
|
||||
const std::string kfooB64("Zm9v");
|
||||
const std::string kfoobB64("Zm9vYg==");
|
||||
const std::string kfoobaB64("Zm9vYmE=");
|
||||
const std::string kfoobarB64("Zm9vYmFy");
|
||||
|
||||
// Arbitrary clear test vectors
|
||||
const std::string kMultipleOf24BitsData("Good day!");
|
||||
const std::string kOneByteOverData("Hello Friend!");
|
||||
const std::string kTwoBytesOverData("Hello Friend!!");
|
||||
const std::string kTestData =
|
||||
"\030\361\\\366\267> \331\210\360\\-\311:\324\256\376"
|
||||
"\261\234\241\326d\326\177\346\346\223\333Y\305\214\330";
|
||||
|
||||
// Arbitrary encoded test vectors
|
||||
const std::string kMultipleOf24BitsB64Data("R29vZCBkYXkh");
|
||||
const std::string kOneByteOverB64Data("SGVsbG8gRnJpZW5kIQ==");
|
||||
const std::string kTwoBytesOverB64Data("SGVsbG8gRnJpZW5kISE=");
|
||||
const std::string kB64TestData = "GPFc9rc+INmI8FwtyTrUrv6xnKHWZNZ/5uaT21nFjNg=";
|
||||
|
||||
const std::pair<const std::string*, const std::string*> kBase64TestVectors[] = {
|
||||
make_pair(&kNullString, &kNullString),
|
||||
make_pair(&kf, &kfB64),
|
||||
make_pair(&kfo, &kfoB64),
|
||||
make_pair(&kfoo, &kfooB64),
|
||||
make_pair(&kfoob, &kfoobB64),
|
||||
make_pair(&kfooba, &kfoobaB64),
|
||||
make_pair(&kfoobar, &kfoobarB64),
|
||||
make_pair(&kMultipleOf24BitsData, &kMultipleOf24BitsB64Data),
|
||||
make_pair(&kOneByteOverData, &kOneByteOverB64Data),
|
||||
make_pair(&kTwoBytesOverData, &kTwoBytesOverB64Data),
|
||||
make_pair(&kTestData, &kB64TestData)};
|
||||
|
||||
const std::string kBase64ErrorVectors[] = {"Foo$sa", "Foo\x99\x23\xfa\02",
|
||||
"Foo==Foo", "FooBa"};
|
||||
|
||||
std::string ConvertToBase64WebSafe(const std::string& std_base64_string) {
|
||||
std::string str(std_base64_string);
|
||||
for (size_t i = 0; i < str.size(); ++i) {
|
||||
if (str[i] == '+')
|
||||
str[i] = '-';
|
||||
else if (str[i] == '/')
|
||||
str[i] = '_';
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class Base64EncodeDecodeTest
|
||||
: public ::testing::TestWithParam<
|
||||
std::pair<const std::string*, const std::string*> > {};
|
||||
|
||||
TEST_P(Base64EncodeDecodeTest, EncodeDecodeTest) {
|
||||
std::pair<const std::string*, const std::string*> values = GetParam();
|
||||
std::vector<uint8_t> decoded_vector = Base64Decode(values.second->data());
|
||||
std::string decoded_string(decoded_vector.begin(), decoded_vector.end());
|
||||
EXPECT_STREQ(values.first->data(), decoded_string.data());
|
||||
std::string b64_string = Base64Encode(decoded_vector);
|
||||
EXPECT_STREQ(values.second->data(), b64_string.data());
|
||||
}
|
||||
|
||||
TEST_P(Base64EncodeDecodeTest, WebSafeEncodeDecodeTest) {
|
||||
std::pair<const std::string*, const std::string*> values = GetParam();
|
||||
std::string encoded_string = ConvertToBase64WebSafe(*(values.second));
|
||||
std::vector<uint8_t> decoded_vector = Base64SafeDecode(encoded_string);
|
||||
std::string decoded_string(decoded_vector.begin(), decoded_vector.end());
|
||||
EXPECT_STREQ(values.first->data(), decoded_string.data());
|
||||
std::string b64_string = Base64SafeEncode(decoded_vector);
|
||||
EXPECT_STREQ(encoded_string.data(), b64_string.data());
|
||||
}
|
||||
|
||||
class Base64ErrorDecodeTest : public ::testing::TestWithParam<std::string> {};
|
||||
|
||||
TEST_P(Base64ErrorDecodeTest, EncoderErrors) {
|
||||
std::vector<uint8_t> result = Base64Decode(GetParam());
|
||||
EXPECT_EQ(0u, result.size());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(ExecutesBase64Test, Base64EncodeDecodeTest,
|
||||
::testing::ValuesIn(kBase64TestVectors));
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(ExecutesBase64Test, Base64ErrorDecodeTest,
|
||||
::testing::ValuesIn(kBase64ErrorVectors));
|
||||
|
||||
class HtoNLL64Test : public ::testing::Test {};
|
||||
|
||||
TEST_F(HtoNLL64Test, PositiveNumber) {
|
||||
uint8_t data[8] = {1, 2, 3, 4, 5, 6, 7, 8};
|
||||
int64_t* network_byte_order = reinterpret_cast<int64_t*>(data);
|
||||
int64_t host_byte_order = htonll64(*network_byte_order);
|
||||
EXPECT_EQ(0x0102030405060708, host_byte_order);
|
||||
}
|
||||
TEST_F(HtoNLL64Test, NegativeNumber) {
|
||||
uint8_t data[8] = {0xfe, 2, 3, 4, 5, 6, 7, 8};
|
||||
int64_t* network_byte_order = reinterpret_cast<int64_t*>(data);
|
||||
int64_t host_byte_order = htonll64(*network_byte_order);
|
||||
EXPECT_EQ(-0x01FdFcFbFaF9F8F8, host_byte_order);
|
||||
}
|
||||
} // namespace wvcdm
|
||||
178
util/test/file_store_unittest.cpp
Normal file
178
util/test/file_store_unittest.cpp
Normal file
@@ -0,0 +1,178 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "cdm_random.h"
|
||||
#include "file_store.h"
|
||||
#include "test_vectors.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
namespace {
|
||||
const std::string kTestDirName = "test_dir";
|
||||
const std::string kTestFileName = "test.txt";
|
||||
const std::string kTestFileName2 = "test2.txt";
|
||||
const std::string kTestFileName3 = "test3.other";
|
||||
const std::string kTestFileNameExt = ".txt";
|
||||
const std::string kTestFileNameExt3 = ".other";
|
||||
const std::string kWildcard = "*";
|
||||
} // namespace
|
||||
|
||||
class FileTest : public testing::Test {
|
||||
protected:
|
||||
FileTest() {}
|
||||
|
||||
void TearDown() override { RemoveTestDir(); }
|
||||
|
||||
void RemoveTestDir() {
|
||||
EXPECT_TRUE(file_system.Remove(test_vectors::kTestDir));
|
||||
}
|
||||
|
||||
FileSystem file_system;
|
||||
};
|
||||
|
||||
TEST_F(FileTest, FileExists) {
|
||||
EXPECT_TRUE(file_system.Exists(test_vectors::kExistentFile));
|
||||
EXPECT_TRUE(file_system.Exists(test_vectors::kExistentDir));
|
||||
EXPECT_FALSE(file_system.Exists(test_vectors::kNonExistentFile));
|
||||
EXPECT_FALSE(file_system.Exists(test_vectors::kNonExistentDir));
|
||||
}
|
||||
|
||||
TEST_F(FileTest, RemoveDir) {
|
||||
EXPECT_TRUE(file_system.Remove(test_vectors::kTestDir));
|
||||
EXPECT_FALSE(file_system.Exists(test_vectors::kTestDir));
|
||||
}
|
||||
|
||||
TEST_F(FileTest, OpenFile) {
|
||||
std::string path = test_vectors::kTestDir + kTestFileName;
|
||||
EXPECT_TRUE(file_system.Remove(path));
|
||||
|
||||
std::unique_ptr<File> file = file_system.Open(path, FileSystem::kCreate);
|
||||
ASSERT_TRUE(file);
|
||||
|
||||
EXPECT_TRUE(file_system.Exists(path));
|
||||
}
|
||||
|
||||
TEST_F(FileTest, RemoveDirAndFile) {
|
||||
std::string path = test_vectors::kTestDir + kTestFileName;
|
||||
|
||||
std::unique_ptr<File> file = file_system.Open(path, FileSystem::kCreate);
|
||||
ASSERT_TRUE(file);
|
||||
|
||||
EXPECT_TRUE(file_system.Exists(path));
|
||||
EXPECT_TRUE(file_system.Remove(path));
|
||||
EXPECT_FALSE(file_system.Exists(path));
|
||||
|
||||
file = file_system.Open(path, FileSystem::kCreate);
|
||||
ASSERT_TRUE(file);
|
||||
|
||||
EXPECT_TRUE(file_system.Exists(path));
|
||||
RemoveTestDir();
|
||||
EXPECT_FALSE(file_system.Exists(test_vectors::kTestDir));
|
||||
EXPECT_FALSE(file_system.Exists(path));
|
||||
}
|
||||
|
||||
TEST_F(FileTest, RemoveWildcardFiles) {
|
||||
std::string path1 = test_vectors::kTestDir + kTestFileName;
|
||||
std::string path2 = test_vectors::kTestDir + kTestFileName2;
|
||||
std::string wildcard_path =
|
||||
test_vectors::kTestDir + kWildcard + kTestFileNameExt;
|
||||
|
||||
std::unique_ptr<File> file = file_system.Open(path1, FileSystem::kCreate);
|
||||
ASSERT_TRUE(file);
|
||||
file = file_system.Open(path2, FileSystem::kCreate);
|
||||
ASSERT_TRUE(file);
|
||||
|
||||
EXPECT_TRUE(file_system.Exists(path1));
|
||||
EXPECT_TRUE(file_system.Exists(path2));
|
||||
EXPECT_TRUE(file_system.Remove(wildcard_path));
|
||||
EXPECT_FALSE(file_system.Exists(path1));
|
||||
EXPECT_FALSE(file_system.Exists(path2));
|
||||
}
|
||||
|
||||
TEST_F(FileTest, FileSize) {
|
||||
std::string path = test_vectors::kTestDir + kTestFileName;
|
||||
file_system.Remove(path);
|
||||
|
||||
std::string write_data = CdmRandom::RandomData(600);
|
||||
size_t write_data_size = write_data.size();
|
||||
std::unique_ptr<File> file = file_system.Open(path, FileSystem::kCreate);
|
||||
ASSERT_TRUE(file);
|
||||
EXPECT_EQ(file->Write(write_data.data(), write_data_size), write_data_size);
|
||||
EXPECT_TRUE(file_system.Exists(path));
|
||||
|
||||
EXPECT_EQ(static_cast<ssize_t>(write_data_size), file_system.FileSize(path));
|
||||
}
|
||||
|
||||
TEST_F(FileTest, WriteReadBinaryFile) {
|
||||
std::string path = test_vectors::kTestDir + kTestFileName;
|
||||
file_system.Remove(path);
|
||||
|
||||
std::string write_data = CdmRandom::RandomData(600);
|
||||
size_t write_data_size = write_data.size();
|
||||
std::unique_ptr<File> file = file_system.Open(path, FileSystem::kCreate);
|
||||
ASSERT_TRUE(file);
|
||||
EXPECT_EQ(file->Write(write_data.data(), write_data_size), write_data_size);
|
||||
EXPECT_TRUE(file_system.Exists(path));
|
||||
|
||||
std::string read_data;
|
||||
read_data.resize(file_system.FileSize(path));
|
||||
size_t read_data_size = read_data.size();
|
||||
file = file_system.Open(path, FileSystem::kReadOnly);
|
||||
ASSERT_TRUE(file);
|
||||
EXPECT_EQ(file->Read(&read_data[0], read_data_size), read_data_size);
|
||||
EXPECT_EQ(write_data, read_data);
|
||||
}
|
||||
|
||||
TEST_F(FileTest, ListFiles) {
|
||||
std::vector<std::string> names;
|
||||
|
||||
std::string not_path("zzz");
|
||||
std::string path1 = test_vectors::kTestDir + kTestFileName;
|
||||
std::string path2 = test_vectors::kTestDir + kTestFileName2;
|
||||
std::string path3 = test_vectors::kTestDir + kTestFileName3;
|
||||
std::string path_dir = test_vectors::kTestDir;
|
||||
|
||||
std::unique_ptr<File> file = file_system.Open(path1, FileSystem::kCreate);
|
||||
ASSERT_TRUE(file);
|
||||
file = file_system.Open(path2, FileSystem::kCreate);
|
||||
ASSERT_TRUE(file);
|
||||
file = file_system.Open(path3, FileSystem::kCreate);
|
||||
ASSERT_TRUE(file);
|
||||
|
||||
EXPECT_TRUE(file_system.Exists(path1));
|
||||
EXPECT_TRUE(file_system.Exists(path2));
|
||||
EXPECT_TRUE(file_system.Exists(path3));
|
||||
|
||||
// Ask for non-existent path.
|
||||
EXPECT_FALSE(file_system.List(not_path, &names));
|
||||
|
||||
// Valid path, but no way to return names.
|
||||
EXPECT_FALSE(file_system.List(path_dir, nullptr));
|
||||
|
||||
// Valid path, valid return.
|
||||
EXPECT_TRUE(file_system.List(path_dir, &names));
|
||||
|
||||
// Should find three files. Order not important.
|
||||
EXPECT_EQ(3u, names.size());
|
||||
EXPECT_THAT(names, ::testing::UnorderedElementsAre(
|
||||
kTestFileName, kTestFileName2, kTestFileName3));
|
||||
|
||||
std::string wild_card_path = path_dir + kWildcard + kTestFileNameExt;
|
||||
EXPECT_TRUE(file_system.Remove(wild_card_path));
|
||||
EXPECT_TRUE(file_system.List(path_dir, &names));
|
||||
|
||||
EXPECT_EQ(1u, names.size());
|
||||
EXPECT_TRUE(names[0].compare(kTestFileName3) == 0);
|
||||
|
||||
std::string wild_card_path2 = path_dir + kWildcard + kTestFileNameExt3;
|
||||
EXPECT_TRUE(file_system.Remove(wild_card_path2));
|
||||
EXPECT_TRUE(file_system.List(path_dir, &names));
|
||||
|
||||
EXPECT_EQ(0u, names.size());
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
40
util/test/test_clock.cpp
Normal file
40
util/test/test_clock.cpp
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Clock - A fake clock just for running tests.
|
||||
|
||||
#include "clock.h"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include "test_sleep.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
namespace {
|
||||
// A fake clock that only advances when TestSleep::Sleep is called.
|
||||
class FakeClock : public wvcdm::TestSleep::CallBack {
|
||||
public:
|
||||
FakeClock() {
|
||||
auto now = std::chrono::steady_clock().now();
|
||||
now_ = now.time_since_epoch() / std::chrono::milliseconds(1);
|
||||
TestSleep::set_callback(this);
|
||||
}
|
||||
~FakeClock() { TestSleep::set_callback(nullptr); }
|
||||
void ElapseTime(int64_t milliseconds) { now_ += milliseconds; }
|
||||
|
||||
int64_t now() const { return now_; }
|
||||
|
||||
private:
|
||||
int64_t now_;
|
||||
};
|
||||
|
||||
FakeClock* g_fake_clock = nullptr;
|
||||
} // namespace
|
||||
|
||||
// On devices running a fake OEMCrypto, we can use a fake sleep and fake time.
|
||||
int64_t Clock::GetCurrentTime() {
|
||||
if (g_fake_clock == nullptr) g_fake_clock = new FakeClock();
|
||||
return g_fake_clock->now() / 1000;
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
44
util/test/test_sleep.cpp
Normal file
44
util/test/test_sleep.cpp
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.
|
||||
|
||||
#include "test_sleep.h"
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include "clock.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
bool TestSleep::real_sleep_ = true;
|
||||
TestSleep::CallBack* TestSleep::callback_ = nullptr;
|
||||
|
||||
void TestSleep::Sleep(unsigned int seconds) {
|
||||
int64_t milliseconds = 1000 * seconds;
|
||||
if (real_sleep_) {
|
||||
// This next bit of logic is to avoid slow drift apart of the real clock and
|
||||
// the fake clock. We compute how far from the real clock has advanced in
|
||||
// total since the start, and then compare to a running total of sleep
|
||||
// calls. We sleep for approximately x second, and then advance the clock by
|
||||
// the amount of time that has actually passed.
|
||||
static auto start_real = std::chrono::steady_clock().now();
|
||||
static int64_t fake_clock = 0;
|
||||
sleep(seconds);
|
||||
auto now_real = std::chrono::steady_clock().now();
|
||||
int64_t total_real = (now_real - start_real) / std::chrono::milliseconds(1);
|
||||
// We want to advance the fake clock by the difference between the real
|
||||
// clock, and the previous value on the fake clock.
|
||||
milliseconds = total_real - fake_clock;
|
||||
fake_clock += milliseconds;
|
||||
}
|
||||
if (callback_ != nullptr) callback_->ElapseTime(milliseconds);
|
||||
}
|
||||
|
||||
void TestSleep::SyncFakeClock() {
|
||||
// Syncing can be done by sleeping 0 seconds.
|
||||
Sleep(0);
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
49
util/test/test_sleep.h
Normal file
49
util/test/test_sleep.h
Normal file
@@ -0,0 +1,49 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
//
|
||||
// TestSleep - Controls sleep and clock adjustment during tests.
|
||||
//
|
||||
#ifndef WVCDM_UTIL_TEST_SLEEP_H_
|
||||
#define WVCDM_UTIL_TEST_SLEEP_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class TestSleep {
|
||||
public:
|
||||
// The callback is called when the clock should be advanced.
|
||||
class CallBack {
|
||||
public:
|
||||
virtual void ElapseTime(int64_t milliseconds) = 0;
|
||||
|
||||
protected:
|
||||
virtual ~CallBack(){};
|
||||
};
|
||||
|
||||
// If real_sleep_ is true, then this sleeps for |seconds| of time.
|
||||
// If the callback exists, this calls the callback.
|
||||
static void Sleep(unsigned int seconds);
|
||||
|
||||
// If we are using a real clock and a fake clock, then the real clock advances
|
||||
// a little while we are doing work, but the fake one only advances when we
|
||||
// sleep. This function advances the fake clock to be in sync with the real
|
||||
// clock. This function should be called to prevent a slow flaky test from
|
||||
// failing due to this drift.
|
||||
static void SyncFakeClock();
|
||||
|
||||
static void set_real_sleep(bool real_sleep) { real_sleep_ = real_sleep; }
|
||||
|
||||
static void set_callback(CallBack* callback) { callback_ = callback; }
|
||||
|
||||
private:
|
||||
// Controls if the test sleep should use real sleep.
|
||||
static bool real_sleep_;
|
||||
// Called when the clock should advance.
|
||||
static CallBack* callback_;
|
||||
};
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
#endif // WVCDM_UTIL_TEST_SLEEP_H_
|
||||
Reference in New Issue
Block a user