OEMCrypto v16.2
Merge from Widevine repo of http://go/wvgerrit/93404 This is the unit tests, reference code, and documentation for OEMCrypto v16.2. Backwards compatibility should work for a v15 OEMCrypto. Some review comments will be addressed in future CLs. Bug: 141247171 Test: Unit tests Test: Media GTS tests on bonito Change-Id: I9d427c07580e180c0a4cfdc4a68f538d351c0ddd
This commit is contained in:
@@ -12,12 +12,12 @@
|
||||
#include "odk_serialize.h"
|
||||
#include "odk_structs.h"
|
||||
#include "odk_structs_priv.h"
|
||||
#include "odk_util.h"
|
||||
#include "serialization_base.h"
|
||||
|
||||
#define ODK_LICENSE_REQUEST_SIZE 20
|
||||
#define ODK_RENEWAL_REQUEST_SIZE 28
|
||||
#define ODK_PROVISIONING_REQUEST_SIZE 88
|
||||
#define OEC_API_VERSION 16
|
||||
|
||||
/* @ private odk functions */
|
||||
|
||||
@@ -26,8 +26,8 @@ static OEMCryptoResult ODK_PrepareRequest(uint8_t* buffer, size_t buffer_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) {
|
||||
if (nonce_values == NULL || core_message_length == NULL ||
|
||||
core_message == NULL || *core_message_length > buffer_length) {
|
||||
return ODK_ERROR_CORE_MESSAGE;
|
||||
}
|
||||
|
||||
@@ -43,17 +43,20 @@ static OEMCryptoResult ODK_PrepareRequest(uint8_t* buffer, size_t buffer_length,
|
||||
switch (message_type) {
|
||||
case ODK_License_Request_Type: {
|
||||
core_message->message_length = ODK_LICENSE_REQUEST_SIZE;
|
||||
Pack_ODK_PreparedLicense(msg, (ODK_PreparedLicense*)core_message);
|
||||
Pack_ODK_PreparedLicenseRequest(
|
||||
msg, (ODK_PreparedLicenseRequest*)core_message);
|
||||
break;
|
||||
}
|
||||
case ODK_Renewal_Request_Type: {
|
||||
core_message->message_length = ODK_RENEWAL_REQUEST_SIZE;
|
||||
Pack_ODK_RenewalMessage(msg, (ODK_RenewalMessage*)core_message);
|
||||
Pack_ODK_PreparedRenewalRequest(
|
||||
msg, (ODK_PreparedRenewalRequest*)core_message);
|
||||
break;
|
||||
}
|
||||
case ODK_Provisioning_Request_Type: {
|
||||
core_message->message_length = ODK_PROVISIONING_REQUEST_SIZE;
|
||||
Pack_ODK_ProvisioningMessage(msg, (ODK_ProvisioningMessage*)core_message);
|
||||
Pack_ODK_PreparedProvisioningRequest(
|
||||
msg, (ODK_PreparedProvisioningRequest*)core_message);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
@@ -93,7 +96,7 @@ static OEMCryptoResult ODK_ParseResponse(const uint8_t* buf,
|
||||
break;
|
||||
}
|
||||
case ODK_Renewal_Response_Type: {
|
||||
Unpack_ODK_RenewalMessage(msg, (ODK_RenewalMessage*)core_message);
|
||||
Unpack_ODK_RenewalResponse(msg, (ODK_RenewalResponse*)core_message);
|
||||
break;
|
||||
}
|
||||
case ODK_Provisioning_Response_Type: {
|
||||
@@ -114,7 +117,10 @@ static OEMCryptoResult ODK_ParseResponse(const uint8_t* buf,
|
||||
|
||||
if (nonce_values) {
|
||||
/* always verify nonce_values for Renewal and Provisioning responses */
|
||||
if (nonce_values->api_version != core_message->nonce_values.api_version ||
|
||||
if (nonce_values->api_major_version !=
|
||||
core_message->nonce_values.api_major_version ||
|
||||
nonce_values->api_minor_version !=
|
||||
core_message->nonce_values.api_minor_version ||
|
||||
nonce_values->nonce != core_message->nonce_values.nonce ||
|
||||
nonce_values->session_id != core_message->nonce_values.session_id) {
|
||||
return OEMCrypto_ERROR_INVALID_NONCE;
|
||||
@@ -131,7 +137,7 @@ static OEMCryptoResult ODK_ParseResponse(const uint8_t* buf,
|
||||
OEMCryptoResult ODK_PrepareCoreLicenseRequest(
|
||||
uint8_t* message, size_t message_length, size_t* core_message_length,
|
||||
const ODK_NonceValues* nonce_values) {
|
||||
ODK_PreparedLicense license_request = {
|
||||
ODK_PreparedLicenseRequest license_request = {
|
||||
{0},
|
||||
};
|
||||
return ODK_PrepareRequest(message, message_length, core_message_length,
|
||||
@@ -139,13 +145,17 @@ OEMCryptoResult ODK_PrepareCoreLicenseRequest(
|
||||
&license_request.core_message);
|
||||
}
|
||||
|
||||
OEMCryptoResult ODK_PrepareCoreRenewalRequest(
|
||||
uint8_t* message, size_t message_length, size_t* core_message_size,
|
||||
const ODK_NonceValues* nonce_values, ODK_ClockValues* clock_values,
|
||||
uint64_t system_time_seconds) {
|
||||
ODK_RenewalMessage renewal_request = {
|
||||
{0},
|
||||
};
|
||||
OEMCryptoResult ODK_PrepareCoreRenewalRequest(uint8_t* message,
|
||||
size_t message_length,
|
||||
size_t* core_message_size,
|
||||
ODK_NonceValues* nonce_values,
|
||||
ODK_ClockValues* clock_values,
|
||||
uint64_t system_time_seconds) {
|
||||
if (nonce_values == NULL || clock_values == NULL)
|
||||
return ODK_ERROR_CORE_MESSAGE;
|
||||
ODK_PreparedRenewalRequest renewal_request = {{0}, 0};
|
||||
/* First, we compute the time this request was made relative to the playback
|
||||
* clock. */
|
||||
if (clock_values->time_of_first_decrypt == 0) {
|
||||
/* It is OK to preemptively request a renewal before playback starts.
|
||||
* We'll treat this as asking for a renewal at playback time 0. */
|
||||
@@ -158,7 +168,9 @@ OEMCryptoResult ODK_PrepareCoreRenewalRequest(
|
||||
return ODK_ERROR_CORE_MESSAGE;
|
||||
}
|
||||
}
|
||||
/* Save time for this request so that we can verify the response. */
|
||||
/* Save time for this request so that we can verify the response. This makes
|
||||
* all earlier requests invalid. If preparing this request fails, then all
|
||||
* requests will be bad. */
|
||||
clock_values->time_of_renewal_request = renewal_request.playback_time;
|
||||
return ODK_PrepareRequest(message, message_length, core_message_size,
|
||||
ODK_Renewal_Request_Type, nonce_values,
|
||||
@@ -169,7 +181,9 @@ OEMCryptoResult ODK_PrepareCoreProvisioningRequest(
|
||||
uint8_t* message, size_t message_length, size_t* core_message_length,
|
||||
const ODK_NonceValues* nonce_values, const uint8_t* device_id,
|
||||
size_t device_id_length) {
|
||||
ODK_ProvisioningMessage provisioning_request = {
|
||||
ODK_PreparedProvisioningRequest provisioning_request = {
|
||||
{0},
|
||||
0,
|
||||
{0},
|
||||
};
|
||||
if (device_id_length > sizeof(provisioning_request.device_id)) {
|
||||
@@ -192,55 +206,61 @@ OEMCryptoResult ODK_ParseLicense(
|
||||
const uint8_t* request_hash, ODK_TimerLimits* timer_limits,
|
||||
ODK_ClockValues* clock_values, ODK_NonceValues* nonce_values,
|
||||
ODK_ParsedLicense* parsed_license) {
|
||||
if (!message || !request_hash || !timer_limits || !clock_values ||
|
||||
!nonce_values || !parsed_license) {
|
||||
if (message == NULL || request_hash == NULL || timer_limits == NULL ||
|
||||
clock_values == NULL || nonce_values == NULL || parsed_license == NULL) {
|
||||
return ODK_ERROR_CORE_MESSAGE;
|
||||
}
|
||||
|
||||
ODK_LicenseResponse license_response = {{0}, parsed_license};
|
||||
OEMCryptoResult err = ODK_ParseResponse(
|
||||
ODK_LicenseResponse license_response = {{{0}}, parsed_license, {0}};
|
||||
const OEMCryptoResult err = ODK_ParseResponse(
|
||||
message, message_length, core_message_length, ODK_License_Response_Type,
|
||||
NULL, &license_response.core_message);
|
||||
NULL, &license_response.request.core_message);
|
||||
|
||||
if (err != OEMCrypto_SUCCESS) {
|
||||
return err;
|
||||
}
|
||||
|
||||
/* This function should not be used for legacy licenses. */
|
||||
if (license_response.core_message.nonce_values.api_version !=
|
||||
OEC_API_VERSION) {
|
||||
if (license_response.request.core_message.nonce_values.api_major_version !=
|
||||
ODK_MAJOR_VERSION) {
|
||||
return ODK_UNSUPPORTED_API;
|
||||
}
|
||||
|
||||
/* If the license has a provider session token (pst), then OEMCrypto should
|
||||
* have a usage entry loaded. The opposite is also an error. */
|
||||
if ((usage_entry_present && parsed_license->pst.length == 0) ||
|
||||
(!usage_entry_present && parsed_license->pst.length > 0)) {
|
||||
return ODK_ERROR_CORE_MESSAGE;
|
||||
}
|
||||
|
||||
if (parsed_license->nonce_required) {
|
||||
if (initial_license_load) {
|
||||
if (nonce_values->nonce !=
|
||||
license_response.core_message.nonce_values.nonce ||
|
||||
license_response.request.core_message.nonce_values.nonce ||
|
||||
nonce_values->session_id !=
|
||||
license_response.core_message.nonce_values.session_id) {
|
||||
license_response.request.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->nonce =
|
||||
license_response.request.core_message.nonce_values.nonce;
|
||||
nonce_values->session_id =
|
||||
license_response.core_message.nonce_values.session_id;
|
||||
license_response.request.core_message.nonce_values.session_id;
|
||||
}
|
||||
}
|
||||
/* For v16, in order to be backwards compatible with a v15 license server,
|
||||
* OEMCrypto stores a hash of the core license request and only signs the
|
||||
* message body. Here, when we process the license response, we verify that
|
||||
* the server has the same hash of the core request. */
|
||||
if (initial_license_load && memcmp(request_hash, parsed_license->request_hash,
|
||||
ODK_SHA256_HASH_SIZE)) {
|
||||
return ODK_ERROR_CORE_MESSAGE;
|
||||
}
|
||||
/* If the license has a provider session token (pst), then OEMCrypto should
|
||||
* have a usage entry loaded. */
|
||||
if (usage_entry_present && parsed_license->pst.length == 0) {
|
||||
if (initial_license_load &&
|
||||
crypto_memcmp(request_hash, license_response.request_hash,
|
||||
ODK_SHA256_HASH_SIZE)) {
|
||||
return ODK_ERROR_CORE_MESSAGE;
|
||||
}
|
||||
*timer_limits = parsed_license->timer_limits;
|
||||
return err;
|
||||
/* And update the clock values state. */
|
||||
clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED;
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
|
||||
OEMCryptoResult ODK_ParseRenewal(const uint8_t* message, size_t message_length,
|
||||
@@ -250,16 +270,18 @@ OEMCryptoResult ODK_ParseRenewal(const uint8_t* message, size_t message_length,
|
||||
const ODK_TimerLimits* timer_limits,
|
||||
ODK_ClockValues* clock_values,
|
||||
uint64_t* timer_value) {
|
||||
if (!message || !nonce_values || !timer_limits || !clock_values) {
|
||||
if (message == NULL || nonce_values == NULL || timer_limits == NULL ||
|
||||
clock_values == NULL) {
|
||||
return ODK_ERROR_CORE_MESSAGE;
|
||||
}
|
||||
|
||||
ODK_RenewalMessage renewal_response = {
|
||||
{0},
|
||||
ODK_RenewalResponse renewal_response = {
|
||||
{{0}, 0},
|
||||
0,
|
||||
};
|
||||
OEMCryptoResult err = ODK_ParseResponse(
|
||||
message, message_length, core_message_length, ODK_Renewal_Response_Type,
|
||||
nonce_values, &renewal_response.core_message);
|
||||
nonce_values, &renewal_response.request.core_message);
|
||||
|
||||
if (err) {
|
||||
return err;
|
||||
@@ -269,69 +291,51 @@ OEMCryptoResult ODK_ParseRenewal(const uint8_t* message, size_t message_length,
|
||||
* Doc: License Duration and Renewal (Changes for OEMCrypto v16)
|
||||
* Section: Renewal Message
|
||||
*/
|
||||
|
||||
/* If a renewal request is lost in transit, we should throw it out and create
|
||||
* a new one. We use the timestamp to make sure we have the latest request.
|
||||
*/
|
||||
if (clock_values->time_of_renewal_request < renewal_response.playback_time) {
|
||||
if (clock_values->time_of_renewal_request <
|
||||
renewal_response.request.playback_time) {
|
||||
return ODK_STALE_RENEWAL;
|
||||
}
|
||||
|
||||
/* The timer value should be set to the renewal duration. */
|
||||
if (timer_value) {
|
||||
*timer_value = timer_limits->renewal_playback_duration_seconds;
|
||||
}
|
||||
|
||||
if (timer_limits->renewal_playback_duration_seconds == 0) {
|
||||
clock_values->time_when_timer_expires = 0;
|
||||
clock_values->timer_status = ODK_DISABLE_TIMER;
|
||||
return ODK_DISABLE_TIMER;
|
||||
}
|
||||
if (odk_add_overflow_u64(system_time,
|
||||
timer_limits->renewal_playback_duration_seconds,
|
||||
&clock_values->time_when_timer_expires)) {
|
||||
return ODK_ERROR_CORE_MESSAGE;
|
||||
}
|
||||
clock_values->timer_status = ODK_SET_TIMER;
|
||||
return ODK_SET_TIMER;
|
||||
return ODK_ComputeRenewalDuration(timer_limits, clock_values, system_time,
|
||||
renewal_response.renewal_duration_seconds,
|
||||
timer_value);
|
||||
}
|
||||
|
||||
OEMCryptoResult ODK_ParseProvisioning(
|
||||
const uint8_t* message, size_t message_length, size_t core_message_length,
|
||||
const ODK_NonceValues* nonce_values, const uint8_t* device_id,
|
||||
size_t device_id_length, ODK_ParsedProvisioning* parsed_response) {
|
||||
if (!message || !nonce_values || !device_id || !parsed_response) {
|
||||
if (message == NULL || nonce_values == NULL || device_id == NULL ||
|
||||
parsed_response == NULL) {
|
||||
return ODK_ERROR_CORE_MESSAGE;
|
||||
}
|
||||
|
||||
ODK_ProvisioningResponse provisioning_response = {{
|
||||
{0},
|
||||
},
|
||||
ODK_ProvisioningResponse provisioning_response = {{{0}, 0, {0}},
|
||||
parsed_response};
|
||||
if (device_id_length > ODK_DEVICE_ID_LEN_MAX) {
|
||||
return ODK_ERROR_CORE_MESSAGE;
|
||||
}
|
||||
|
||||
const OEMCryptoResult err =
|
||||
OEMCryptoResult err =
|
||||
ODK_ParseResponse(message, message_length, core_message_length,
|
||||
ODK_Provisioning_Response_Type, nonce_values,
|
||||
&provisioning_response.core_provisioning.core_message);
|
||||
&provisioning_response.request.core_message);
|
||||
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (memcmp(device_id, provisioning_response.core_provisioning.device_id,
|
||||
if (memcmp(device_id, provisioning_response.request.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)) {
|
||||
if (memcmp(zero, provisioning_response.request.device_id + device_id_length,
|
||||
ODK_DEVICE_ID_LEN_MAX - device_id_length)) {
|
||||
return ODK_ERROR_CORE_MESSAGE;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user