See https://developers.google.com/widevine/drm/client/opk for documentation and an integration guide. See CHANGELOG.md for details about recent changes.
3058 lines
121 KiB
C
3058 lines
121 KiB
C
/* Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary
|
|
source code may only be used and distributed under the Widevine
|
|
License Agreement. */
|
|
|
|
#include "OEMCryptoCENC.h"
|
|
|
|
#include <inttypes.h>
|
|
#include <string.h>
|
|
|
|
#include "odk.h"
|
|
#include "odk_endian.h"
|
|
#include "odk_util.h"
|
|
#include "oemcrypto_api_macros.h"
|
|
#include "oemcrypto_asymmetric_key_table.h"
|
|
#include "oemcrypto_check_macros.h"
|
|
#include "oemcrypto_compiler_attributes.h"
|
|
#include "oemcrypto_key.h"
|
|
#include "oemcrypto_key_table.h"
|
|
#include "oemcrypto_output.h"
|
|
#include "oemcrypto_overflow.h"
|
|
#include "oemcrypto_session.h"
|
|
#include "oemcrypto_session_key_table.h"
|
|
#include "oemcrypto_session_table.h"
|
|
#include "oemcrypto_usage_table.h"
|
|
#include "wtpi_abort_interface.h"
|
|
#include "wtpi_clock_interface_layer1.h"
|
|
#include "wtpi_config_interface.h"
|
|
#include "wtpi_crypto_and_key_management_interface_layer1.h"
|
|
#include "wtpi_crypto_asymmetric_interface.h"
|
|
#include "wtpi_initialize_terminate_interface.h"
|
|
#include "wtpi_logging_interface.h"
|
|
#include "wtpi_root_of_trust_interface_layer1.h"
|
|
|
|
typedef enum GlobalSystemState {
|
|
SYSTEM_NOT_INITIALIZED = (int)0x2ca77206,
|
|
SYSTEM_INITIALIZED = (int)0xf57fab49
|
|
} GlobalSystemState;
|
|
static GlobalSystemState g_opk_system_state = SYSTEM_NOT_INITIALIZED;
|
|
|
|
static bool IsSubstrInRange(size_t message_length,
|
|
OEMCrypto_Substring substring, bool allow_null) {
|
|
if (!substring.length) return (substring.offset == 0) && allow_null;
|
|
if (substring.offset > message_length) return false;
|
|
|
|
size_t end_of_substring;
|
|
if (OPK_AddOverflowUX(substring.offset, substring.length,
|
|
&end_of_substring)) {
|
|
return false;
|
|
}
|
|
if (end_of_substring > message_length) return false;
|
|
return true;
|
|
}
|
|
|
|
/* Cleanup functions for various OEMCrypto calls. */
|
|
|
|
static OEMCryptoResult FreeMacKeys(OEMCryptoSession* session) {
|
|
ABORT_IF(g_opk_system_state != SYSTEM_INITIALIZED,
|
|
"OEMCrypto is not yet initialized");
|
|
ABORT_IF_NULL(session);
|
|
OEMCryptoResult result = OPKI_FreeKeyFromTable(&session->mac_key_server);
|
|
OEMCryptoResult free_key_result =
|
|
OPKI_FreeKeyFromTable(&session->mac_key_client);
|
|
if (result == OEMCrypto_SUCCESS) result = free_key_result;
|
|
return result;
|
|
}
|
|
|
|
static OEMCryptoResult FreeMacAndEncryptionKeys(OEMCryptoSession* session) {
|
|
ABORT_IF(g_opk_system_state != SYSTEM_INITIALIZED,
|
|
"OEMCrypto is not yet initialized");
|
|
ABORT_IF_NULL(session);
|
|
OEMCryptoResult result = FreeMacKeys(session);
|
|
OEMCryptoResult free_key_result =
|
|
OPKI_FreeKeyFromTable(&session->encryption_key);
|
|
if (result == OEMCrypto_SUCCESS) result = free_key_result;
|
|
return result;
|
|
}
|
|
|
|
static OEMCryptoResult FreeContentAndEntitlementKeys(
|
|
OEMCryptoSession* session) {
|
|
ABORT_IF(g_opk_system_state != SYSTEM_INITIALIZED,
|
|
"OEMCrypto is not yet initialized");
|
|
ABORT_IF_NULL(session);
|
|
OEMCryptoResult result = OEMCrypto_SUCCESS;
|
|
OEMCryptoResult free_key_result = result;
|
|
for (size_t i = 0; i < session->num_content_keys; i++) {
|
|
free_key_result = OPKI_FreeKeyFromTable(&session->content_keys[i]);
|
|
if (result == OEMCrypto_SUCCESS) result = free_key_result;
|
|
}
|
|
for (size_t i = 0; i < session->num_entitlement_keys; i++) {
|
|
free_key_result = OPKI_FreeKeyFromTable(&session->entitlement_keys[i]);
|
|
if (result == OEMCrypto_SUCCESS) result = free_key_result;
|
|
}
|
|
session->num_content_keys = 0;
|
|
session->num_entitlement_keys = 0;
|
|
return result;
|
|
}
|
|
|
|
static bool IsSupportedAsymmetricKeyType(AsymmetricKeyType key_type) {
|
|
return key_type == DRM_RSA_PRIVATE_KEY || key_type == DRM_ECC_PRIVATE_KEY;
|
|
}
|
|
|
|
static OEMCryptoResult RewrapDeviceDRMKeyOEMCert(
|
|
OEMCryptoSession* session_context, const uint8_t* encrypted_message_key,
|
|
size_t encrypted_message_key_length, const uint8_t* enc_drm_key,
|
|
size_t enc_drm_key_length, const uint8_t* enc_drm_key_iv,
|
|
AsymmetricKeyType drm_key_type, uint8_t* wrapped_drm_key,
|
|
size_t wrapped_drm_key_length) {
|
|
ABORT_IF(g_opk_system_state != SYSTEM_INITIALIZED,
|
|
"OEMCrypto is not yet initialized");
|
|
ABORT_IF(WTPI_GetProvisioningMethod() != OEMCrypto_OEMCertificate,
|
|
"This function is only valid on Provisioning 3.0 devices");
|
|
|
|
ABORT_IF_NULL(session_context);
|
|
ABORT_IF_NULL(encrypted_message_key);
|
|
ABORT_IF_ZERO(encrypted_message_key_length);
|
|
ABORT_IF_NULL(enc_drm_key);
|
|
ABORT_IF_ZERO(enc_drm_key_length);
|
|
ABORT_IF(enc_drm_key_length > PKCS8_DRM_KEY_MAX_SIZE,
|
|
"enc_drm_key_length of %zu is too large", enc_drm_key_length);
|
|
ABORT_IF_NULL(enc_drm_key_iv);
|
|
ABORT_IF(!IsSupportedAsymmetricKeyType(drm_key_type),
|
|
"drm_key_type %d is invalid", drm_key_type);
|
|
ABORT_IF_NULL(wrapped_drm_key);
|
|
ABORT_IF_ZERO(wrapped_drm_key_length);
|
|
|
|
// TODO(b/180530495): implement this.
|
|
OEMCryptoResult result = OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
|
|
|
/* RSA decryption needs at most RSA_size to decrypt. 3072 is the largest size
|
|
OEM RSA keys we can use. */
|
|
// uint8_t message_key[KEY_SIZE_3072];
|
|
// size_t message_key_length = sizeof(message_key);
|
|
// DecryptMessageWithOEMPrivateKey(
|
|
// encrypted_message_key, encrypted_message_key_length, message_key,
|
|
// &message_key_length);
|
|
|
|
if (result != OEMCrypto_SUCCESS) goto cleanup;
|
|
// if (message_key_length != KEY_SIZE_128) {
|
|
// /* Encryption key is expected to be an AES 128-bit key. */
|
|
// result = OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
// goto cleanup;
|
|
// }
|
|
|
|
// result = OPKI_CreateKey(&session_context->encryption_key, ENCRYPTION_KEY,
|
|
// KEY_SIZE_128);
|
|
// if (result != OEMCrypto_SUCCESS) goto cleanup;
|
|
// result = WTPI_CreateKeyHandle(message_key, message_key_length,
|
|
// ENCRYPTION_KEY,
|
|
// &(session_context->encryption_key->key_handle));
|
|
// if (result != OEMCrypto_SUCCESS) goto cleanup;
|
|
|
|
// result = RewrapDeviceDRMKeyCommon(
|
|
// session_context, enc_drm_key, enc_drm_key_length, enc_drm_key_iv,
|
|
// drm_key_type, wrapped_drm_key, wrapped_drm_key_length);
|
|
|
|
cleanup:;
|
|
OEMCryptoResult free_key_result =
|
|
OPKI_FreeAsymmetricKeyFromTable(&session_context->drm_private_key);
|
|
if (result == OEMCrypto_SUCCESS) result = free_key_result;
|
|
free_key_result = FreeMacAndEncryptionKeys(session_context);
|
|
if (result == OEMCrypto_SUCCESS) result = free_key_result;
|
|
if (result != OEMCrypto_SUCCESS) session_context->state = SESSION_INVALID;
|
|
return result;
|
|
}
|
|
|
|
// This function contains the RewrapDeviceDRMKey code that is shared between
|
|
// keybox and OEM certificate devices.
|
|
static OEMCryptoResult RewrapDeviceDRMKeyCommon(OEMCryptoSession* session,
|
|
const uint8_t* enc_drm_key,
|
|
size_t enc_drm_key_length,
|
|
const uint8_t* enc_drm_key_iv,
|
|
AsymmetricKeyType drm_key_type,
|
|
uint8_t* wrapped_drm_key,
|
|
size_t wrapped_drm_key_length) {
|
|
ABORT_IF(g_opk_system_state != SYSTEM_INITIALIZED,
|
|
"OEMCrypto is not yet initialized");
|
|
ABORT_IF_NULL(session);
|
|
ABORT_IF_NULL(enc_drm_key);
|
|
ABORT_IF_ZERO(enc_drm_key_length);
|
|
ABORT_IF(enc_drm_key_length > PKCS8_DRM_KEY_MAX_SIZE,
|
|
"enc_drm_key_length of %zu is too large", enc_drm_key_length);
|
|
ABORT_IF_NULL(enc_drm_key_iv);
|
|
ABORT_IF(!IsSupportedAsymmetricKeyType(drm_key_type),
|
|
"drm_key_type %d is invalid", drm_key_type);
|
|
|
|
if (!OPKI_CheckKey(session->encryption_key, ENCRYPTION_KEY)) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
|
|
/* Decrypt and deserialize the DRM private key. */
|
|
uint8_t clear_drm_key[PKCS8_DRM_KEY_MAX_SIZE];
|
|
KeySize key_size;
|
|
OEMCryptoResult result =
|
|
WTPI_K1_GetKeySize(session->encryption_key->key_handle, &key_size);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
result = WTPI_C1_AESCBCDecrypt(
|
|
session->encryption_key->key_handle, (size_t)key_size, enc_drm_key,
|
|
enc_drm_key_length, enc_drm_key_iv, clear_drm_key);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to AES CBC decrypt DRM private key with result: %u", result);
|
|
return result;
|
|
}
|
|
|
|
/* Adjust for RSA keys with specified schemes */
|
|
uint32_t allowed_schemes = 0;
|
|
size_t key_offset = 0;
|
|
if (drm_key_type == DRM_RSA_PRIVATE_KEY) {
|
|
if (enc_drm_key_length >= 8 &&
|
|
crypto_memcmp(clear_drm_key, "SIGN", 4) == 0) {
|
|
memcpy(&allowed_schemes, clear_drm_key + 4, 4);
|
|
allowed_schemes = oemcrypto_be32toh(allowed_schemes);
|
|
key_offset = 8;
|
|
} else {
|
|
allowed_schemes = kSign_RSASSA_PSS;
|
|
}
|
|
}
|
|
|
|
WTPI_AsymmetricKey_Handle private_key_handle;
|
|
result = WTPI_CreateAsymmetricKeyHandle(clear_drm_key + key_offset,
|
|
enc_drm_key_length - key_offset,
|
|
drm_key_type, &private_key_handle);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE(
|
|
"Failed to create asymmetric key handle for DRM private key with "
|
|
"result: %u",
|
|
result);
|
|
return result;
|
|
}
|
|
size_t private_key_size;
|
|
result = WTPI_GetSignatureSize(private_key_handle, &private_key_size);
|
|
WTPI_FreeAsymmetricKeyHandle(private_key_handle);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get DRM private key size with result: %u", result);
|
|
return result;
|
|
}
|
|
|
|
result =
|
|
WTPI_WrapAsymmetricKey(wrapped_drm_key, wrapped_drm_key_length,
|
|
drm_key_type, clear_drm_key, enc_drm_key_length);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to re-wrap DRM private key with result: %u", result);
|
|
return result;
|
|
}
|
|
/* Check that it's a valid DRM key. */
|
|
result = OPKI_LoadDRMKey(session, drm_key_type, wrapped_drm_key,
|
|
wrapped_drm_key_length, private_key_size,
|
|
allowed_schemes);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to load DRM key with result: %u", result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static OEMCryptoResult RewrapDeviceDRMKeyKeybox(
|
|
OEMCryptoSession* session_context, const uint8_t* message,
|
|
size_t message_length, const uint8_t* signature, size_t signature_length,
|
|
const uint8_t* enc_drm_key, size_t enc_drm_key_length,
|
|
const uint8_t* enc_drm_key_iv, AsymmetricKeyType drm_key_type,
|
|
uint8_t* wrapped_drm_key, size_t wrapped_drm_key_length) {
|
|
ABORT_IF(g_opk_system_state != SYSTEM_INITIALIZED,
|
|
"OEMCrypto is not yet initialized");
|
|
ABORT_IF(WTPI_GetProvisioningMethod() != OEMCrypto_Keybox,
|
|
"This function is only valid on Provisioning 2.0 devices");
|
|
|
|
ABORT_IF_NULL(session_context);
|
|
ABORT_IF_NULL(message);
|
|
ABORT_IF_ZERO(message_length);
|
|
ABORT_IF_NULL(signature);
|
|
ABORT_IF_ZERO(signature_length);
|
|
ABORT_IF(signature_length != SHA256_DIGEST_LENGTH,
|
|
"signature_length is not the length of a SHA256 digest");
|
|
ABORT_IF_NULL(enc_drm_key);
|
|
ABORT_IF_ZERO(enc_drm_key_length);
|
|
ABORT_IF(enc_drm_key_length > PKCS8_DRM_KEY_MAX_SIZE,
|
|
"enc_drm_key_length of %zu is too large", enc_drm_key_length);
|
|
ABORT_IF_NULL(enc_drm_key_iv);
|
|
ABORT_IF(!IsSupportedAsymmetricKeyType(drm_key_type),
|
|
"drm_key_type %d is invalid", drm_key_type);
|
|
ABORT_IF_NULL(wrapped_drm_key);
|
|
ABORT_IF_ZERO(wrapped_drm_key_length);
|
|
|
|
/* Use mac_key_server from previous call to GenerateDerivedKeys to verify the
|
|
message. */
|
|
OEMCryptoResult result = OPKI_VerifySignatureWithMacKeyServer(
|
|
session_context, message, message_length, signature);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to verify signature with mac key server, error: %u", result);
|
|
goto cleanup;
|
|
}
|
|
|
|
result = RewrapDeviceDRMKeyCommon(
|
|
session_context, enc_drm_key, enc_drm_key_length, enc_drm_key_iv,
|
|
drm_key_type, wrapped_drm_key, wrapped_drm_key_length);
|
|
|
|
cleanup:;
|
|
OEMCryptoResult free_key_result =
|
|
OPKI_FreeAsymmetricKeyFromTable(&session_context->drm_private_key);
|
|
if (result == OEMCrypto_SUCCESS) result = free_key_result;
|
|
free_key_result = FreeMacAndEncryptionKeys(session_context);
|
|
if (result == OEMCrypto_SUCCESS) result = free_key_result;
|
|
if (result != OEMCrypto_SUCCESS) session_context->state = SESSION_INVALID;
|
|
|
|
return result;
|
|
}
|
|
|
|
static OEMCryptoResult GetDeviceID(uint8_t* device_id,
|
|
size_t* device_id_length) {
|
|
if (device_id_length == NULL) {
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
if (WTPI_GetProvisioningMethod() != OEMCrypto_Keybox) {
|
|
// TODO(b/180530495): Implement this.
|
|
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
if (*device_id_length < KEYBOX_DEVICE_ID_SIZE) {
|
|
*device_id_length = KEYBOX_DEVICE_ID_SIZE;
|
|
return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
}
|
|
if (device_id == NULL) {
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
*device_id_length = KEYBOX_DEVICE_ID_SIZE;
|
|
return WTPI_GetDeviceIDFromKeybox(device_id, *device_id_length);
|
|
}
|
|
|
|
static OEMCryptoResult GetROTSignatureLength(size_t* signature_length) {
|
|
ABORT_IF(g_opk_system_state != SYSTEM_INITIALIZED,
|
|
"OEMCrypto is not yet initialized");
|
|
ABORT_IF_NULL(signature_length);
|
|
const OEMCrypto_ProvisioningMethod provisioning_method =
|
|
WTPI_GetProvisioningMethod();
|
|
if (provisioning_method == OEMCrypto_Keybox) {
|
|
*signature_length = SHA256_DIGEST_LENGTH;
|
|
return OEMCrypto_SUCCESS;
|
|
} else if (provisioning_method == OEMCrypto_OEMCertificate) {
|
|
// TODO(b/180530495): implement this.
|
|
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
|
} else {
|
|
// TODO(b/180530495): implement this.
|
|
/* TODO: Add ECC support. */
|
|
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_SetSandbox(const uint8_t* sandbox_id,
|
|
size_t sandbox_id_length) {
|
|
RETURN_INVALID_CONTEXT_IF_NULL(sandbox_id);
|
|
RETURN_INVALID_CONTEXT_IF_ZERO(sandbox_id_length);
|
|
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_Initialize(void) {
|
|
if (g_opk_system_state != SYSTEM_NOT_INITIALIZED) {
|
|
LOGE("OEMCrypto_Initialize called when not in uninitialized state.");
|
|
LOGE("OEMCrypto will now terminate and re-initialize.");
|
|
OEMCrypto_Terminate();
|
|
}
|
|
|
|
OEMCryptoResult result = WTPI_Initialize();
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("OEMCrypto failed to |WTPI_Initialize| with result: %u", result);
|
|
result = OEMCrypto_ERROR_INIT_FAILED;
|
|
goto cleanup;
|
|
}
|
|
|
|
result = WTPI_InitializeClock();
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("OEMCrypto failed to |WTPI_InitializeClock| with result: %u", result);
|
|
result = OEMCrypto_ERROR_INIT_FAILED;
|
|
goto cleanup;
|
|
}
|
|
|
|
result = WTPI_InitializeKeybox();
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Initializing keybox error %u. System not ready for production.",
|
|
result);
|
|
// This means we don't have a keybox installed. This is fine for a test
|
|
// device or for a device that is still in the factory before it has been
|
|
// provisioned with a keybox. In that case, we log the error and continue.
|
|
// A production system will fail later on when it tries to validate the
|
|
// keybox.
|
|
result = OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OPKI_InitializeSessionTable();
|
|
OPKI_InitializeKeyTable();
|
|
OPKI_InitializeAsymmetricKeyTable();
|
|
|
|
result = WTPI_K1_InitializeKeyManagement();
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to initialize key management with result: %u", result);
|
|
goto cleanup;
|
|
}
|
|
|
|
result = OPKI_InitializeUsageTable();
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to initialize usage table with result: %u", result);
|
|
goto cleanup;
|
|
}
|
|
|
|
g_opk_system_state = SYSTEM_INITIALIZED;
|
|
result = OEMCrypto_SUCCESS;
|
|
|
|
cleanup:
|
|
if (result != OEMCrypto_SUCCESS) OEMCrypto_Terminate();
|
|
|
|
return result;
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_Terminate(void) {
|
|
OEMCryptoResult usage_table_terminate_result = OPKI_TerminateUsageTable();
|
|
OEMCryptoResult session_terminate_result = OPKI_TerminateSessionTable();
|
|
OEMCryptoResult key_terminate_result = OPKI_TerminateKeyTable();
|
|
OEMCryptoResult asymmetric_key_terminate_result =
|
|
OPKI_TerminateAsymmetricKeyTable();
|
|
OEMCryptoResult keybox_result = WTPI_TerminateKeybox();
|
|
OEMCryptoResult clock_result = WTPI_TerminateClock();
|
|
OEMCryptoResult key_management_result = WTPI_K1_TerminateKeyManagement();
|
|
OEMCryptoResult tee_result = WTPI_Terminate();
|
|
g_opk_system_state = SYSTEM_NOT_INITIALIZED;
|
|
if (tee_result != OEMCrypto_SUCCESS) {
|
|
LOGE("OEMCrypto failed to |Terminate| with result: %u", tee_result);
|
|
return tee_result;
|
|
}
|
|
if (keybox_result != OEMCrypto_SUCCESS) {
|
|
return keybox_result;
|
|
}
|
|
if (clock_result != OEMCrypto_SUCCESS) {
|
|
return clock_result;
|
|
}
|
|
if (key_management_result != OEMCrypto_SUCCESS) {
|
|
return key_management_result;
|
|
}
|
|
if (session_terminate_result != OEMCrypto_SUCCESS) {
|
|
return session_terminate_result;
|
|
}
|
|
if (usage_table_terminate_result != OEMCrypto_SUCCESS) {
|
|
return usage_table_terminate_result;
|
|
}
|
|
if (asymmetric_key_terminate_result != OEMCrypto_SUCCESS) {
|
|
return asymmetric_key_terminate_result;
|
|
}
|
|
return key_terminate_result;
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_OpenSession(OEMCrypto_SESSION* session) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
RETURN_INVALID_CONTEXT_IF_NULL(session);
|
|
|
|
OEMCryptoResult result = OPKI_GrabSession(session);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to grab and initialize session with result: %u", result);
|
|
return result;
|
|
}
|
|
|
|
OEMCryptoSession* session_context = NULL;
|
|
result = OPKI_GetSession(*session, &session_context);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get session with result: %u, session = %u", result,
|
|
*session);
|
|
return result;
|
|
}
|
|
ABORT_IF(session_context == NULL,
|
|
"OPKI_GetSession() provided invalid output.");
|
|
result = OPKI_CheckStatePreCall(session_context, API_OPENSESSION);
|
|
if (result != OEMCrypto_SUCCESS) goto cleanup;
|
|
result = OPKI_SetStatePostCall(session_context, API_OPENSESSION);
|
|
|
|
cleanup:
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
OEMCryptoResult ignored UNUSED = OPKI_FreeSession(*session);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_CloseSession(OEMCrypto_SESSION session) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
OEMCryptoSession* session_context = NULL;
|
|
OEMCryptoResult result = OPKI_GetSession(session, &session_context);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get session with result: %u, session = %u", result,
|
|
session);
|
|
return result;
|
|
}
|
|
ABORT_IF(session_context == NULL,
|
|
"OPKI_GetSession() provided invalid output.");
|
|
result = OPKI_CheckStatePreCall(session_context, API_CLOSESESSION);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
return OPKI_FreeSession(session);
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_GenerateDerivedKeys(OEMCrypto_SESSION session,
|
|
const uint8_t* mac_key_context,
|
|
size_t mac_key_context_length,
|
|
const uint8_t* enc_key_context,
|
|
size_t enc_key_context_length) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (WTPI_GetProvisioningMethod() != OEMCrypto_Keybox) {
|
|
LOGD("This function is only valid on Provisioning 2.0 devices");
|
|
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
OEMCryptoSession* session_context = NULL;
|
|
OEMCryptoResult result = OPKI_GetSession(session, &session_context);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get session with result: %u, session = %u", result,
|
|
session);
|
|
return result;
|
|
}
|
|
ABORT_IF(session_context == NULL,
|
|
"OPKI_GetSession() provided invalid output.");
|
|
result = OPKI_CheckStatePreCall(session_context, API_GENERATEDERIVEDKEYS);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
|
|
RETURN_INVALID_CONTEXT_IF_NULL(mac_key_context);
|
|
RETURN_INVALID_CONTEXT_IF_ZERO(mac_key_context_length);
|
|
RETURN_INVALID_CONTEXT_IF_NULL(enc_key_context);
|
|
RETURN_INVALID_CONTEXT_IF_ZERO(enc_key_context_length);
|
|
|
|
WTPI_K1_SymmetricKey_Handle keybox_key = NULL;
|
|
result = WTPI_K1_CreateKeyHandleFromKeybox(&keybox_key);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to create key handle from keybox with result: %u", result);
|
|
goto cleanup;
|
|
}
|
|
result = OPKI_DeriveMacAndEncryptionKeys(
|
|
session_context, keybox_key, mac_key_context, mac_key_context_length,
|
|
enc_key_context, enc_key_context_length);
|
|
if (result == OEMCrypto_SUCCESS) {
|
|
result = OPKI_SetStatePostCall(session_context, API_GENERATEDERIVEDKEYS);
|
|
} else {
|
|
LOGE("Failed to derive mac and encryption keys from keybox with result: %u",
|
|
result);
|
|
}
|
|
cleanup : {
|
|
OEMCryptoResult free_key_result = WTPI_K1_FreeKeyHandle(keybox_key);
|
|
if (result == OEMCrypto_SUCCESS) result = free_key_result;
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
FreeMacAndEncryptionKeys(session_context);
|
|
session_context->state = SESSION_INVALID;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_DeriveKeysFromSessionKey(
|
|
OEMCrypto_SESSION session, const uint8_t* enc_session_key,
|
|
size_t enc_session_key_length, const uint8_t* mac_key_context,
|
|
size_t mac_key_context_length, const uint8_t* enc_key_context,
|
|
size_t enc_key_context_length) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
OEMCryptoSession* session_context = NULL;
|
|
WTPI_K1_SymmetricKey_Handle session_key_handle = NULL;
|
|
OEMCryptoResult result = OPKI_GetSession(session, &session_context);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get session with result: %u, session = %u", result,
|
|
session);
|
|
return result;
|
|
}
|
|
ABORT_IF(session_context == NULL,
|
|
"OPKI_GetSession() provided invalid output.");
|
|
result =
|
|
OPKI_CheckStatePreCall(session_context, API_DERIVEKEYSFROMSESSIONKEY);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
|
|
RETURN_INVALID_CONTEXT_IF_NULL(enc_session_key);
|
|
RETURN_INVALID_CONTEXT_IF_ZERO(enc_session_key_length);
|
|
RETURN_INVALID_CONTEXT_IF_NULL(mac_key_context);
|
|
RETURN_INVALID_CONTEXT_IF_ZERO(mac_key_context_length);
|
|
RETURN_INVALID_CONTEXT_IF_NULL(enc_key_context);
|
|
RETURN_INVALID_CONTEXT_IF_ZERO(enc_key_context_length);
|
|
|
|
/* Decrypt the session key with the DRM key and then derive keys from it.
|
|
RSA decryption needs at most RSA_size to decrypt. 3072 is the largest size
|
|
DRM RSA keys we can use. */
|
|
uint8_t session_key[KEY_SIZE_3072];
|
|
size_t session_key_length = sizeof(session_key);
|
|
size_t expected_session_key_length = 0;
|
|
result = OEMCrypto_ERROR_DEVICE_NOT_RSA_PROVISIONED;
|
|
if (session_context->drm_private_key == NULL) goto cleanup;
|
|
WTPI_AsymmetricKey_Handle private_key_handle;
|
|
uint32_t allowed_schemes;
|
|
AsymmetricKey* private_key = session_context->drm_private_key;
|
|
result = WTPI_UnwrapIntoAsymmetricKeyHandle(
|
|
private_key->wrapped_key, private_key->wrapped_key_length,
|
|
private_key->key_type, &private_key_handle, &allowed_schemes);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to unwrap DRM private key into key handle with result: %u",
|
|
result);
|
|
return result;
|
|
}
|
|
switch (session_context->drm_private_key->key_type) {
|
|
case DRM_RSA_PRIVATE_KEY: {
|
|
if (session_context->allowed_schemes != kSign_RSASSA_PSS) {
|
|
LOGE("Bad RSA padding scheme: %u", session_context->allowed_schemes);
|
|
WTPI_FreeAsymmetricKeyHandle(private_key_handle);
|
|
return OEMCrypto_ERROR_INVALID_RSA_KEY;
|
|
}
|
|
result = WTPI_RSADecrypt(private_key_handle, enc_session_key,
|
|
enc_session_key_length, session_key,
|
|
&session_key_length);
|
|
expected_session_key_length = KEY_SIZE_128;
|
|
} break;
|
|
case DRM_ECC_PRIVATE_KEY: {
|
|
result = WTPI_ECCDeriveSessionKey(private_key_handle, enc_session_key,
|
|
enc_session_key_length, session_key,
|
|
&session_key_length);
|
|
expected_session_key_length = KEY_SIZE_256;
|
|
} break;
|
|
}
|
|
WTPI_FreeAsymmetricKeyHandle(private_key_handle);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE(
|
|
"Failed to decrypt session key with the DRM private key with result: "
|
|
"%u",
|
|
result);
|
|
goto cleanup;
|
|
}
|
|
ABORT_IF_ZERO(expected_session_key_length);
|
|
if (session_key_length != expected_session_key_length) {
|
|
LOGE("Invalid session key length: %zu, expected = %zu", session_key_length,
|
|
expected_session_key_length);
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
result = WTPI_K1_CreateKeyHandle(session_key, session_key_length,
|
|
DERIVING_KEY, &session_key_handle);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to create key handle type DERIVING_KEY with result: %u",
|
|
result);
|
|
goto cleanup;
|
|
}
|
|
|
|
result = OPKI_DeriveMacAndEncryptionKeys(
|
|
session_context, session_key_handle, mac_key_context,
|
|
mac_key_context_length, enc_key_context, enc_key_context_length);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE(
|
|
"Failed to derive mac and encryption keys from session key with "
|
|
"result: %u",
|
|
result);
|
|
goto cleanup;
|
|
}
|
|
|
|
result = OPKI_SetStatePostCall(session_context, API_DERIVEKEYSFROMSESSIONKEY);
|
|
|
|
cleanup:;
|
|
OEMCryptoResult free_key_result =
|
|
OPKI_FreeAsymmetricKeyFromTable(&session_context->drm_private_key);
|
|
if (result == OEMCrypto_SUCCESS) result = free_key_result;
|
|
free_key_result = WTPI_K1_FreeKeyHandle(session_key_handle);
|
|
if (result == OEMCrypto_SUCCESS) result = free_key_result;
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
FreeMacAndEncryptionKeys(session_context);
|
|
session_context->state = SESSION_INVALID;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_GenerateNonce(OEMCrypto_SESSION session,
|
|
uint32_t* nonce) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
RETURN_INVALID_CONTEXT_IF_NULL(nonce);
|
|
|
|
OEMCryptoSession* session_context = NULL;
|
|
OEMCryptoResult result = OPKI_GetSession(session, &session_context);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get session with result: %u, session = %u", result,
|
|
session);
|
|
return result;
|
|
}
|
|
ABORT_IF(session_context == NULL,
|
|
"OPKI_GetSession() provided invalid output.");
|
|
result = OPKI_CheckStatePreCall(session_context, API_GENERATENONCE);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
|
|
/* Get the current time. */
|
|
uint64_t now;
|
|
result = WTPI_GetTrustedTime(&now);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get trusted time with result: %u", result);
|
|
return result;
|
|
}
|
|
|
|
/* last_nonce_time should only be initialized once. */
|
|
static uint64_t last_nonce_time = 0;
|
|
static int nonce_count = 0;
|
|
const int nonce_flood_count = 200;
|
|
if (last_nonce_time == now) {
|
|
nonce_count++;
|
|
if (nonce_count > nonce_flood_count) {
|
|
LOGE("Nonce flood detected: now = %" PRIu64, now);
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
} else {
|
|
nonce_count = 1;
|
|
last_nonce_time = now;
|
|
}
|
|
|
|
/* Generate a new nonce that doesn't collide with other nonces. */
|
|
uint32_t nonce_value = 0;
|
|
while (nonce_value == 0 || OPKI_NonceCollision(nonce_value)) {
|
|
result = WTPI_C1_RandomBytes((uint8_t*)(&nonce_value), sizeof(nonce_value));
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to generate random nonce with result: %u", result);
|
|
return result;
|
|
}
|
|
}
|
|
result = OPKI_SetNonce(session_context, nonce_value);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
*nonce = nonce_value;
|
|
|
|
return OPKI_SetStatePostCall(session_context, API_GENERATENONCE);
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_LoadSRM(const uint8_t* buffer UNUSED,
|
|
size_t buffer_length UNUSED) {
|
|
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
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 (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
OEMCryptoSession* session_context = NULL;
|
|
OEMCryptoResult result = OPKI_GetSession(session, &session_context);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get session with result: %u, session = %u", result,
|
|
session);
|
|
return result;
|
|
}
|
|
ABORT_IF(session_context == NULL,
|
|
"OPKI_GetSession() provided invalid output.");
|
|
result = OPKI_CheckStatePreCall(session_context,
|
|
API_PREPANDSIGN_PROVISION_REQUEST);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
RETURN_INVALID_CONTEXT_IF_NULL(core_message_length);
|
|
RETURN_INVALID_CONTEXT_IF_NULL(signature_length);
|
|
if (session_context->request_signed) {
|
|
LOGE("Attempt to sign provision request after license request");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
|
|
size_t required_signature_size;
|
|
result = GetROTSignatureLength(&required_signature_size);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
uint8_t device_id[ODK_DEVICE_ID_LEN_MAX] = {0};
|
|
size_t device_id_length = ODK_DEVICE_ID_LEN_MAX;
|
|
result = GetDeviceID(device_id, &device_id_length);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get device id with result: %u", result);
|
|
return result;
|
|
}
|
|
result = ODK_PrepareCoreProvisioningRequest(
|
|
message, message_length, core_message_length,
|
|
&session_context->nonce_values, device_id, device_id_length);
|
|
if (*signature_length < required_signature_size ||
|
|
result == OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
*signature_length = required_signature_size;
|
|
/* The core_message_length has been correctly set by
|
|
* ODK_PrepareCoreProvisioningRequest, but the message and signature buffer
|
|
* will be initialized and filled in the subsequent call. */
|
|
return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
}
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("ODK error: %u", result);
|
|
return result;
|
|
}
|
|
RETURN_INVALID_CONTEXT_IF_NULL(message);
|
|
RETURN_INVALID_CONTEXT_IF_ZERO(message_length);
|
|
RETURN_INVALID_CONTEXT_IF_NULL(signature);
|
|
const OEMCrypto_ProvisioningMethod provisioning_method =
|
|
WTPI_GetProvisioningMethod();
|
|
if (provisioning_method == OEMCrypto_Keybox) {
|
|
result = OPKI_GenerateSignatureWithMacKeyClient(
|
|
session_context, message, message_length, signature, signature_length);
|
|
} else if (provisioning_method == OEMCrypto_OEMCertificate) {
|
|
result = OPKI_GenerateCertSignature(session_context, message,
|
|
message_length, signature,
|
|
signature_length, CERT_SIGNATURE_OEM);
|
|
} else {
|
|
LOGE("Bad provision method = %#x", WTPI_GetProvisioningMethod());
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
if (result != OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
LOGE("Failed to generate signature with result: %u", result);
|
|
}
|
|
return result;
|
|
}
|
|
session_context->request_signed = true;
|
|
return OPKI_SetStatePostCall(session_context,
|
|
API_PREPANDSIGN_PROVISION_REQUEST);
|
|
}
|
|
|
|
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 (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
OEMCryptoSession* session_context = NULL;
|
|
OEMCryptoResult result = OPKI_GetSession(session, &session_context);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get session with result: %u, session = %u", result,
|
|
session);
|
|
return result;
|
|
}
|
|
ABORT_IF(session_context == NULL,
|
|
"OPKI_GetSession() provided invalid output.");
|
|
result =
|
|
OPKI_CheckStatePreCall(session_context, API_PREPANDSIGN_LICENSE_REQUEST);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
RETURN_INVALID_CONTEXT_IF_NULL(core_message_length);
|
|
RETURN_INVALID_CONTEXT_IF_NULL(signature_length);
|
|
ABORT_IF_NULL(session_context->drm_private_key);
|
|
size_t required_signature_size = session_context->drm_private_key->key_size;
|
|
result = ODK_PrepareCoreLicenseRequest(message, message_length,
|
|
core_message_length,
|
|
&session_context->nonce_values);
|
|
if (*signature_length < required_signature_size ||
|
|
result == OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
*signature_length = required_signature_size;
|
|
/* The core_message_length has been correctly set by
|
|
* ODK_PrepareCoreLicenseRequest, but the message and signature buffer will
|
|
* be initialized and filled in the subsequent call. */
|
|
return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
}
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("ODK error: %u", result);
|
|
return result;
|
|
}
|
|
RETURN_INVALID_CONTEXT_IF_NULL(message);
|
|
if (message_length < *core_message_length) {
|
|
LOGE("message_length of %zu is too small for core message length %zu",
|
|
message_length, *core_message_length);
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
RETURN_INVALID_CONTEXT_IF_NULL(signature);
|
|
if (session_context->request_signed) {
|
|
LOGE("Attempt to sign two license requests");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
/* For backwards compatibility, we only sign the message body, and we compute
|
|
* a SHA256 of the core message. */
|
|
result = WTPI_C1_SHA256(message, *core_message_length,
|
|
session_context->license_request_hash);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to compute SHA256 of the core message with result: %u",
|
|
result);
|
|
return result;
|
|
}
|
|
const uint8_t* message_body = message + *core_message_length;
|
|
const size_t message_body_length = message_length - *core_message_length;
|
|
result = OPKI_GenerateCertSignature(session_context, message_body,
|
|
message_body_length, signature,
|
|
signature_length, CERT_SIGNATURE_DRM);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to generate cert signature with result: %u", result);
|
|
return result;
|
|
}
|
|
|
|
uint64_t now;
|
|
result = WTPI_GetTrustedTime(&now);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get trusted time with result: %u", result);
|
|
return result;
|
|
}
|
|
result = ODK_InitializeClockValues(&session_context->clock_values, now);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to initialize clock values with result: %u", result);
|
|
return result;
|
|
}
|
|
session_context->request_signed = true;
|
|
return OPKI_SetStatePostCall(session_context,
|
|
API_PREPANDSIGN_LICENSE_REQUEST);
|
|
}
|
|
|
|
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 (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
OEMCryptoSession* session_context = NULL;
|
|
OEMCryptoResult result = OPKI_GetSession(session, &session_context);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get session with result: %u, session = %u", result,
|
|
session);
|
|
return result;
|
|
}
|
|
ABORT_IF(session_context == NULL,
|
|
"OPKI_GetSession() provided invalid output.");
|
|
result =
|
|
OPKI_CheckStatePreCall(session_context, API_PREPANDSIGN_RENEWAL_REQUEST);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
RETURN_INVALID_CONTEXT_IF_NULL(core_message_length);
|
|
RETURN_INVALID_CONTEXT_IF_NULL(signature_length);
|
|
/* 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. So it is OK if we have never signed a license request. */
|
|
if (session_context->request_signed && !session_context->response_loaded) {
|
|
LOGE("Attempt to sign renewal before load");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
const size_t required_signature_size = SHA256_DIGEST_LENGTH;
|
|
uint64_t now;
|
|
result = WTPI_GetTrustedTime(&now);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
result = ODK_PrepareCoreRenewalRequest(
|
|
message, message_length, core_message_length,
|
|
&session_context->nonce_values, &session_context->clock_values, now);
|
|
if (*signature_length < required_signature_size ||
|
|
result == OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
*signature_length = required_signature_size;
|
|
/* The core_message_length has been correctly set by
|
|
* ODK_PrepareCoreRenewalRequest, but the message and signature buffer will
|
|
* be initialized and filled in the subsequent call. */
|
|
return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
}
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("ODK error: %u", result);
|
|
return result;
|
|
}
|
|
RETURN_INVALID_CONTEXT_IF_NULL(message);
|
|
if (message_length < *core_message_length) {
|
|
LOGE("message_length of %zu is too short; expected at least %zu",
|
|
message_length, *core_message_length);
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
RETURN_INVALID_CONTEXT_IF_NULL(signature);
|
|
if (session_context->nonce_values.api_major_version < API_MAJOR_VERSION) {
|
|
/* If we are talking to an old license server, then we only sign the message
|
|
* body. */
|
|
const uint8_t* message_body = message + *core_message_length;
|
|
const size_t message_body_length = message_length - *core_message_length;
|
|
if (OPKI_CheckKey(session_context->mac_key_client, MAC_KEY_CLIENT)) {
|
|
/* Sign with mac key client if it is available. */
|
|
result = OPKI_GenerateSignatureWithMacKeyClient(
|
|
session_context, message_body, message_body_length, signature,
|
|
signature_length);
|
|
} else {
|
|
/* Otherwise, this is a release request. Use the usage entry to sign. */
|
|
result = OPKI_SignReleaseRequest(session_context->session_id,
|
|
message_body, message_body_length,
|
|
signature, signature_length);
|
|
}
|
|
} else {
|
|
/* Sign the entire message. */
|
|
if (OPKI_CheckKey(session_context->mac_key_client, MAC_KEY_CLIENT)) {
|
|
result = OPKI_GenerateSignatureWithMacKeyClient(session_context, message,
|
|
message_length, signature,
|
|
signature_length);
|
|
} else {
|
|
result =
|
|
OPKI_SignReleaseRequest(session_context->session_id, message,
|
|
message_length, signature, signature_length);
|
|
}
|
|
}
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
if (result != OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
LOGE(
|
|
"Failed to generate signature or sign release request with result: "
|
|
"%u",
|
|
result);
|
|
}
|
|
return result;
|
|
}
|
|
return OPKI_SetStatePostCall(session_context,
|
|
API_PREPANDSIGN_RENEWAL_REQUEST);
|
|
}
|
|
|
|
static OEMCryptoResult LoadKeysNoSignature(
|
|
OEMCryptoSession* session, const uint8_t* message, size_t message_length,
|
|
OEMCrypto_Substring enc_mac_keys_iv, OEMCrypto_Substring enc_mac_keys,
|
|
size_t num_keys, const OEMCrypto_KeyObject* key_array,
|
|
OEMCrypto_Substring pst, OEMCrypto_Substring srm_restriction_data,
|
|
OEMCrypto_LicenseType license_type) {
|
|
ABORT_IF(g_opk_system_state != SYSTEM_INITIALIZED,
|
|
"OEMCrypto is not yet initialized");
|
|
ABORT_IF_NULL(session);
|
|
ABORT_IF_NULL(message);
|
|
ABORT_IF_ZERO(message_length);
|
|
ABORT_IF_NULL(key_array);
|
|
RETURN_INVALID_CONTEXT_IF_NULL(key_array);
|
|
RETURN_INVALID_CONTEXT_IF_ZERO(num_keys);
|
|
|
|
if (session->response_loaded) {
|
|
return OEMCrypto_ERROR_LICENSE_RELOAD;
|
|
}
|
|
|
|
if (!IsSubstrInRange(message_length, enc_mac_keys_iv, true) ||
|
|
!IsSubstrInRange(message_length, enc_mac_keys, true) ||
|
|
!IsSubstrInRange(message_length, pst, true) ||
|
|
!IsSubstrInRange(message_length, srm_restriction_data, true)) {
|
|
LOGE("Invalid substring range");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
|
|
for (size_t i = 0; i < num_keys; i++) {
|
|
if (!IsSubstrInRange(message_length, key_array[i].key_id, false) ||
|
|
!IsSubstrInRange(message_length, key_array[i].key_data, false) ||
|
|
!IsSubstrInRange(message_length, key_array[i].key_data_iv, false) ||
|
|
!IsSubstrInRange(message_length, key_array[i].key_control, false) ||
|
|
!IsSubstrInRange(message_length, key_array[i].key_control_iv, false) ||
|
|
key_array[i].key_id.length == 0 ||
|
|
key_array[i].key_id.length > KEY_ID_MAX_SIZE ||
|
|
key_array[i].key_data_iv.length < KEY_IV_SIZE ||
|
|
key_array[i].key_control.length < KEY_CONTROL_SIZE ||
|
|
key_array[i].key_control_iv.length < KEY_IV_SIZE) {
|
|
LOGE("Invalid substring range for key index: %zu", i);
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
}
|
|
/* If enc_mac_keys or enc_mac_keys_iv is present, both must be present and
|
|
must be the right sizes. */
|
|
if ((enc_mac_keys_iv.length > 0 || enc_mac_keys.length > 0) &&
|
|
(enc_mac_keys_iv.length < KEY_IV_SIZE ||
|
|
enc_mac_keys.length < 2 * MAC_KEY_SIZE)) {
|
|
LOGE("Invalid encrypted MAC key or MAC key IV length");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
|
|
/* Check to see if the MAC Keys' IV is directly before the MAC Keys, which is
|
|
forbidden. */
|
|
if (enc_mac_keys.offset >= KEY_IV_SIZE && enc_mac_keys.length > 0 &&
|
|
crypto_memcmp(message + enc_mac_keys.offset - KEY_IV_SIZE,
|
|
message + enc_mac_keys_iv.offset, KEY_IV_SIZE) == 0) {
|
|
LOGE("The bytes before the mac keys shouldn't be the same as the iv");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
|
|
/* If we've already loaded in a license and it isn't the current license type,
|
|
fail. */
|
|
if (session->state == SESSION_LICENSE_LOADED &&
|
|
session->license_type != license_type) {
|
|
LOGE(
|
|
"License type doesn't match. License type already loaded: %u, license "
|
|
"type to be loaded: %u",
|
|
session->license_type, license_type);
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
|
|
OEMCryptoResult result = OEMCrypto_SUCCESS;
|
|
session->license_type = license_type;
|
|
session->refresh_valid =
|
|
IsSubstrInRange(message_length, enc_mac_keys, false) &&
|
|
IsSubstrInRange(message_length, enc_mac_keys_iv, false);
|
|
|
|
session->valid_srm_version = false;
|
|
if (srm_restriction_data.length != 0) {
|
|
static const uint8_t kSrmVerification[8] = {'H', 'D', 'C', 'P',
|
|
'D', 'A', 'T', 'A'};
|
|
uint32_t minimum_version_network;
|
|
if (srm_restriction_data.length <
|
|
sizeof(kSrmVerification) + sizeof(minimum_version_network)) {
|
|
LOGE("Bad SRM restriction data length: %zu", srm_restriction_data.length);
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
|
|
if (crypto_memcmp(message + srm_restriction_data.offset, kSrmVerification,
|
|
sizeof(kSrmVerification))) {
|
|
LOGE("SRM verification failed");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
|
|
const uint8_t* const minimum_version_pointer =
|
|
message + srm_restriction_data.offset + sizeof(kSrmVerification);
|
|
memcpy(&minimum_version_network, minimum_version_pointer,
|
|
sizeof(minimum_version_network));
|
|
const uint32_t minimum_version = oemcrypto_be32toh(minimum_version_network);
|
|
|
|
uint32_t current_version = 0;
|
|
result = WTPI_GetCurrentSRMVersion(¤t_version);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Unable to fetch SRM data with error: %u", result);
|
|
} else if (current_version < minimum_version) {
|
|
LOGE("SRM Version is too small %u, required: %u", current_version,
|
|
minimum_version);
|
|
} else {
|
|
session->valid_srm_version = true;
|
|
}
|
|
}
|
|
|
|
/* Decrypt and install keys in key object. Each key will have a key control
|
|
block. They will all have the same nonce. */
|
|
session->num_content_keys = 0;
|
|
session->num_entitlement_keys = 0;
|
|
for (size_t i = 0; i < num_keys; i++) {
|
|
result = OPKI_InstallKey(
|
|
session, message + key_array[i].key_id.offset,
|
|
key_array[i].key_id.length, message + key_array[i].key_data.offset,
|
|
key_array[i].key_data.length, message + key_array[i].key_data_iv.offset,
|
|
message + key_array[i].key_control.offset,
|
|
message + key_array[i].key_control_iv.offset);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to install key with result: %u, index = %zu", result, i);
|
|
break;
|
|
}
|
|
if (session->license_type == OEMCrypto_ContentLicense) {
|
|
session->num_content_keys++;
|
|
} else {
|
|
session->num_entitlement_keys++;
|
|
}
|
|
}
|
|
if (result != OEMCrypto_SUCCESS) goto cleanup;
|
|
if (session->refresh_valid) {
|
|
result = OPKI_UpdateMacKeys(session, message + enc_mac_keys.offset,
|
|
message + enc_mac_keys_iv.offset);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to update mac keys with result: %u", result);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
/* If we have loaded a usage entry, or we should have loaded an entry, check
|
|
* it. */
|
|
if (OPKI_GetUsageEntryStatus(session->session_id) != USAGE_ENTRY_NONE ||
|
|
pst.length > 0) {
|
|
if (pst.length == 0) {
|
|
LOGE("Usage entry exists but PST doesn't");
|
|
result = OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
goto cleanup;
|
|
}
|
|
switch (OPKI_GetUsageEntryStatus(session->session_id)) {
|
|
case USAGE_ENTRY_NONE:
|
|
default:
|
|
/* PST specified, but no usage entry loaded. */
|
|
LOGE("PST specified, but no usage entry loaded");
|
|
result = OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
break;
|
|
case USAGE_ENTRY_NEW:
|
|
/* This was the first time loading the license. Copy the pst and mac
|
|
* keys. */
|
|
result = OPKI_SetUsageEntryPST(session->session_id,
|
|
message + pst.offset, pst.length);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to set usage entry PST with result: %u", result);
|
|
break;
|
|
}
|
|
if (session->mac_key_server == NULL ||
|
|
session->mac_key_client == NULL) {
|
|
result = OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
break;
|
|
}
|
|
result = OPKI_SetUsageEntryMacKeys(session->session_id,
|
|
session->mac_key_server->key_handle,
|
|
session->mac_key_client->key_handle);
|
|
break;
|
|
case USAGE_ENTRY_DEACTIVATED:
|
|
result = OEMCrypto_ERROR_LICENSE_INACTIVE;
|
|
break;
|
|
case USAGE_ENTRY_LOADED:
|
|
/* This was not the first time loading the license. Verify the pst and
|
|
* mac keys. */
|
|
result = OPKI_VerfiyUsageEntryPST(session->session_id,
|
|
message + pst.offset, pst.length);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE(
|
|
"Failed to verify usage entry PST with result: %u, session_id = "
|
|
"%u",
|
|
result, session->session_id);
|
|
break;
|
|
}
|
|
if (session->mac_key_server == NULL ||
|
|
session->mac_key_client == NULL) {
|
|
result = OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
break;
|
|
}
|
|
result = OPKI_VerifyUsageEntryMacKeys(
|
|
session->session_id, session->mac_key_server->key_handle,
|
|
session->mac_key_client->key_handle);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE(
|
|
"Failed to verify usage entry mac keys with result: %u, "
|
|
"session_id = %u",
|
|
result, session->session_id);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
cleanup:;
|
|
OEMCryptoResult free_key_result =
|
|
OPKI_FreeKeyFromTable(&session->encryption_key);
|
|
if (result == OEMCrypto_SUCCESS) result = free_key_result;
|
|
if (result != OEMCrypto_SUCCESS || !session->refresh_valid) {
|
|
/* Mac keys must be freed in every case except when the call is successful
|
|
AND we are given new mac keys. In that case, the new keys should be
|
|
preserved in the session for future RefreshKeys calls. */
|
|
free_key_result = FreeMacKeys(session);
|
|
if (result == OEMCrypto_SUCCESS) result = free_key_result;
|
|
}
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
FreeContentAndEntitlementKeys(session);
|
|
session->state = SESSION_INVALID;
|
|
}
|
|
if (result == OEMCrypto_SUCCESS) session->response_loaded = true;
|
|
|
|
return result;
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_LoadLicense(OEMCrypto_SESSION session,
|
|
const uint8_t* message,
|
|
size_t message_length,
|
|
size_t core_message_length,
|
|
const uint8_t* signature,
|
|
size_t signature_length) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
OEMCryptoSession* session_context = NULL;
|
|
OEMCryptoResult result = OPKI_GetSession(session, &session_context);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get session with result: %u, session = %u", result,
|
|
session);
|
|
return result;
|
|
}
|
|
ABORT_IF(session_context == NULL,
|
|
"OPKI_GetSession() provided invalid output.");
|
|
result = OPKI_CheckStatePreCall(session_context, API_LOADLICENSE);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
RETURN_INVALID_CONTEXT_IF_NULL(message);
|
|
RETURN_INVALID_CONTEXT_IF_ZERO(message_length);
|
|
RETURN_INVALID_CONTEXT_IF_NULL(signature);
|
|
if (signature_length != SHA256_DIGEST_LENGTH) {
|
|
LOGE("signature_length is not the length of a SHA256 digest");
|
|
return OEMCrypto_ERROR_SIGNATURE_FAILURE;
|
|
}
|
|
/* Check state before we check signature. State is change in
|
|
* LoadKeysNoSignature. */
|
|
if (session_context->response_loaded) {
|
|
return OEMCrypto_ERROR_LICENSE_RELOAD;
|
|
}
|
|
ODK_ParsedLicense parsed_response;
|
|
const bool initial_license_load =
|
|
(OPKI_GetUsageEntryStatus(session_context->session_id) !=
|
|
USAGE_ENTRY_LOADED);
|
|
const bool usage_entry_present =
|
|
(OPKI_GetUsageEntryStatus(session_context->session_id) !=
|
|
USAGE_ENTRY_NONE);
|
|
result = ODK_ParseLicense(
|
|
message, message_length, core_message_length, initial_license_load,
|
|
usage_entry_present, session_context->license_request_hash,
|
|
&session_context->timer_limits, &session_context->clock_values,
|
|
&session_context->nonce_values, &parsed_response);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to parse license with result: %u", result);
|
|
return result;
|
|
}
|
|
|
|
if (parsed_response.license_type != OEMCrypto_ContentLicense &&
|
|
parsed_response.license_type != OEMCrypto_EntitlementLicense) {
|
|
LOGE("Invalid license type: %u", parsed_response.license_type);
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
result = OPKI_VerifySignatureWithMacKeyServer(session_context, message,
|
|
message_length, signature);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to verify signature with server mac key, error: %u", result);
|
|
return result;
|
|
}
|
|
|
|
const uint8_t* message_body = message + core_message_length;
|
|
const size_t message_body_length = message_length - core_message_length;
|
|
result = LoadKeysNoSignature(
|
|
session_context, message_body, message_body_length,
|
|
parsed_response.enc_mac_keys_iv, parsed_response.enc_mac_keys,
|
|
parsed_response.key_array_length, parsed_response.key_array,
|
|
parsed_response.pst, parsed_response.srm_restriction_data,
|
|
parsed_response.license_type);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to load keys with result: %u", result);
|
|
return result;
|
|
}
|
|
return OPKI_SetStatePostCall(session_context, API_LOADLICENSE);
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_LoadKeys(
|
|
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
|
|
const uint8_t* signature, size_t signature_length,
|
|
OEMCrypto_Substring enc_mac_keys_iv, OEMCrypto_Substring enc_mac_keys,
|
|
size_t key_array_length, const OEMCrypto_KeyObject* key_array,
|
|
OEMCrypto_Substring pst, OEMCrypto_Substring srm_restriction_data,
|
|
OEMCrypto_LicenseType license_type) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
OEMCryptoSession* session_context = NULL;
|
|
OEMCryptoResult result = OPKI_GetSession(session, &session_context);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get session with result: %u, session = %u", result,
|
|
session);
|
|
return result;
|
|
}
|
|
ABORT_IF(session_context == NULL,
|
|
"OPKI_GetSession() provided invalid output.");
|
|
result = OPKI_CheckStatePreCall(session_context, API_LOADKEYS);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
|
|
RETURN_INVALID_CONTEXT_IF_NULL(message);
|
|
RETURN_INVALID_CONTEXT_IF_ZERO(message_length);
|
|
RETURN_INVALID_CONTEXT_IF_NULL(signature);
|
|
if (signature_length != SHA256_DIGEST_LENGTH) {
|
|
LOGE("signature_length is not the length of a SHA256 digest");
|
|
return OEMCrypto_ERROR_SIGNATURE_FAILURE;
|
|
}
|
|
RETURN_INVALID_CONTEXT_IF_NULL(key_array);
|
|
RETURN_INVALID_CONTEXT_IF_ZERO(key_array_length);
|
|
|
|
if (license_type != OEMCrypto_ContentLicense &&
|
|
license_type != OEMCrypto_EntitlementLicense) {
|
|
LOGE("Invalid license type: %u", license_type);
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
|
|
/* Check state before we check signature. State is change in
|
|
* LoadKeysNoSignature. */
|
|
if (session_context->response_loaded) {
|
|
return OEMCrypto_ERROR_LICENSE_RELOAD;
|
|
}
|
|
|
|
result = OPKI_VerifySignatureWithMacKeyServer(session_context, message,
|
|
message_length, signature);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to verify signature with server mac key, error: %u", result);
|
|
return result;
|
|
}
|
|
|
|
result = LoadKeysNoSignature(
|
|
session_context, message, message_length, enc_mac_keys_iv, enc_mac_keys,
|
|
key_array_length, key_array, pst, srm_restriction_data, license_type);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
|
|
/* Update clock values using the duration for the loaded key. */
|
|
SymmetricKey* key = NULL;
|
|
const int kIndex = 0;
|
|
switch (session_context->license_type) {
|
|
case OEMCrypto_ContentLicense:
|
|
if (session_context->num_content_keys == 0) {
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
key = session_context->content_keys[kIndex];
|
|
break;
|
|
case OEMCrypto_EntitlementLicense:
|
|
if (session_context->num_entitlement_keys == 0) {
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
key = session_context->entitlement_keys[kIndex];
|
|
break;
|
|
default:
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
const uint32_t duration = key ? key->key_control_block.duration : 0;
|
|
uint64_t now;
|
|
result = WTPI_GetTrustedTime(&now);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get trusted time with result: %u", result);
|
|
return result;
|
|
}
|
|
result = ODK_InitializeV15Values(
|
|
&session_context->timer_limits, &session_context->clock_values,
|
|
&session_context->nonce_values, duration, now);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
return OPKI_SetStatePostCall(session_context, API_LOADKEYS);
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_LoadEntitledContentKeys(
|
|
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
|
|
size_t key_array_length,
|
|
const OEMCrypto_EntitledContentKeyObject* key_array) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
OEMCryptoSession* session_context = NULL;
|
|
OEMCryptoResult result = OPKI_GetSession(session, &session_context);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get session with result: %u, session = %u", result,
|
|
session);
|
|
return result;
|
|
}
|
|
ABORT_IF(session_context == NULL,
|
|
"OPKI_GetSession() provided invalid output.");
|
|
result = OPKI_CheckStatePreCall(session_context, API_LOADENTITLEDCONTENTKEYS);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
|
|
RETURN_INVALID_CONTEXT_IF_NULL(message);
|
|
RETURN_INVALID_CONTEXT_IF_ZERO(message_length);
|
|
RETURN_INVALID_CONTEXT_IF_NULL(key_array);
|
|
RETURN_INVALID_CONTEXT_IF_ZERO(key_array_length);
|
|
|
|
if (session_context->license_type != OEMCrypto_EntitlementLicense ||
|
|
session_context->num_entitlement_keys == 0) {
|
|
LOGE("Not a entitlement license or no keys found");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
|
|
for (size_t i = 0; i < key_array_length; i++) {
|
|
if (!IsSubstrInRange(message_length, key_array[i].entitlement_key_id,
|
|
false) ||
|
|
!IsSubstrInRange(message_length, key_array[i].content_key_id, false) ||
|
|
!IsSubstrInRange(message_length, key_array[i].content_key_data,
|
|
false) ||
|
|
!IsSubstrInRange(message_length, key_array[i].content_key_data_iv,
|
|
false) ||
|
|
key_array[i].entitlement_key_id.length > KEY_ID_MAX_SIZE ||
|
|
key_array[i].content_key_id.length > KEY_ID_MAX_SIZE ||
|
|
key_array[i].content_key_data.length != KEY_SIZE_128 ||
|
|
key_array[i].content_key_data_iv.length != KEY_IV_SIZE) {
|
|
LOGE("Invalid substring range for key index: %zu", i);
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
}
|
|
|
|
result = OEMCrypto_SUCCESS;
|
|
for (size_t i = 0; i < key_array_length; i++) {
|
|
const OEMCrypto_EntitledContentKeyObject key_object = key_array[i];
|
|
SymmetricKey* entitlement_key = OPKI_FindKeyFromTable(
|
|
session_context, false, message + key_object.entitlement_key_id.offset,
|
|
key_object.entitlement_key_id.length);
|
|
if (entitlement_key == NULL) {
|
|
result = OEMCrypto_KEY_NOT_ENTITLED;
|
|
goto cleanup;
|
|
}
|
|
|
|
uint32_t content_key_index = session_context->num_content_keys;
|
|
if (entitlement_key->has_entitled_content_key) {
|
|
/* Free previous content key associated with this entitlement key. */
|
|
content_key_index = entitlement_key->entitled_content_key_index;
|
|
result = OPKI_FreeKeyFromTable(
|
|
&session_context->content_keys[content_key_index]);
|
|
if (result != OEMCrypto_SUCCESS) goto cleanup;
|
|
}
|
|
|
|
/* If we're not updating an existing content key and we can't add any more,
|
|
we fail. */
|
|
bool adding_new_content_key =
|
|
content_key_index == session_context->num_content_keys;
|
|
if (adding_new_content_key &&
|
|
session_context->num_content_keys == CONTENT_KEYS_PER_SESSION) {
|
|
result = OEMCrypto_ERROR_INSUFFICIENT_RESOURCES;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!OPKI_CheckKey(entitlement_key, ENTITLEMENT_KEY)) {
|
|
result = OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
goto cleanup;
|
|
}
|
|
SymmetricKey** content_key_ptr =
|
|
&session_context->content_keys[content_key_index];
|
|
result = OPKI_CreateKey(content_key_ptr, CONTENT_KEY,
|
|
(KeySize)key_object.content_key_data.length);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to create CONTENT_KEY with result: %u", result);
|
|
goto cleanup;
|
|
}
|
|
SymmetricKey* content_key = *content_key_ptr;
|
|
result = WTPI_K1_AESDecryptAndCreateKeyHandle(
|
|
entitlement_key->key_handle,
|
|
message + key_object.content_key_data.offset,
|
|
key_object.content_key_data.length,
|
|
message + key_object.content_key_data_iv.offset, CONTENT_KEY,
|
|
&(content_key->key_handle));
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to decrypt and create content key handle with result %u",
|
|
result);
|
|
goto cleanup;
|
|
}
|
|
|
|
memcpy(content_key->key_id, message + key_object.content_key_id.offset,
|
|
key_object.content_key_id.length);
|
|
content_key->key_id_size = key_object.content_key_id.length;
|
|
content_key->session_key_index = content_key_index;
|
|
|
|
/* Associate the new content key with its entitlement key. */
|
|
content_key->is_entitled_content_key = true;
|
|
content_key->entitlement_key_index = entitlement_key->session_key_index;
|
|
|
|
/* Associate the entitlement key with this new content key. */
|
|
entitlement_key->has_entitled_content_key = true;
|
|
entitlement_key->entitled_content_key_index = content_key_index;
|
|
|
|
if (adding_new_content_key) session_context->num_content_keys++;
|
|
} // for loop over key_array
|
|
|
|
result = OPKI_SetStatePostCall(session_context, API_LOADENTITLEDCONTENTKEYS);
|
|
|
|
cleanup:
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
FreeContentAndEntitlementKeys(session_context);
|
|
session_context->state = SESSION_INVALID;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_LoadRenewal(OEMCrypto_SESSION session,
|
|
const uint8_t* message,
|
|
size_t message_length,
|
|
size_t core_message_length,
|
|
const uint8_t* signature,
|
|
size_t signature_length) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
OEMCryptoSession* session_context = NULL;
|
|
OEMCryptoResult result = OPKI_GetSession(session, &session_context);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get session with result: %u, session = %u", result,
|
|
session);
|
|
return result;
|
|
}
|
|
ABORT_IF(session_context == NULL,
|
|
"OPKI_GetSession() provided invalid output.");
|
|
result = OPKI_CheckStatePreCall(session_context, API_LOADRENEWAL);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
|
|
RETURN_INVALID_CONTEXT_IF_NULL(message);
|
|
RETURN_INVALID_CONTEXT_IF_ZERO(message_length);
|
|
RETURN_INVALID_CONTEXT_IF_NULL(signature);
|
|
if (signature_length != SHA256_DIGEST_LENGTH) {
|
|
LOGE("signature_length is not the length of a SHA256 digest");
|
|
return OEMCrypto_ERROR_SIGNATURE_FAILURE;
|
|
}
|
|
|
|
result = OPKI_VerifySignatureWithMacKeyServer(session_context, message,
|
|
message_length, signature);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to verify signature with server mac key, error: %u", result);
|
|
return result;
|
|
}
|
|
|
|
uint64_t now;
|
|
result = WTPI_GetTrustedTime(&now);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get trusted time with result: %u", result);
|
|
return result;
|
|
}
|
|
uint64_t* timer_value = NULL;
|
|
result = ODK_ParseRenewal(message, message_length, core_message_length,
|
|
&session_context->nonce_values, now,
|
|
&session_context->timer_limits,
|
|
&session_context->clock_values, timer_value);
|
|
if (result == ODK_TIMER_EXPIRED) return OEMCrypto_ERROR_KEY_EXPIRED;
|
|
if (result == ODK_SET_TIMER || result == ODK_DISABLE_TIMER) {
|
|
return OPKI_SetStatePostCall(session_context, API_LOADRENEWAL);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_RefreshKeys(
|
|
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
|
|
const uint8_t* signature, size_t signature_length, size_t key_array_length,
|
|
const OEMCrypto_KeyRefreshObject* key_array) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
OEMCryptoSession* session_context = NULL;
|
|
OEMCryptoResult result = OPKI_GetSession(session, &session_context);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get session with result: %u, session = %u", result,
|
|
session);
|
|
return result;
|
|
}
|
|
ABORT_IF(session_context == NULL,
|
|
"OPKI_GetSession() provided invalid output.");
|
|
result = OPKI_CheckStatePreCall(session_context, API_REFRESHKEYS);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
|
|
RETURN_INVALID_CONTEXT_IF_NULL(message);
|
|
RETURN_INVALID_CONTEXT_IF_ZERO(message_length);
|
|
RETURN_INVALID_CONTEXT_IF_NULL(signature);
|
|
if (signature_length != SHA256_DIGEST_LENGTH) {
|
|
LOGE("signature_length is not the length of a SHA256 digest");
|
|
return OEMCrypto_ERROR_SIGNATURE_FAILURE;
|
|
}
|
|
RETURN_INVALID_CONTEXT_IF_NULL(key_array);
|
|
RETURN_INVALID_CONTEXT_IF_ZERO(key_array_length);
|
|
|
|
/* We only use the first key object to update the entire license. Since we
|
|
* know num_keys > 0 after the last if statement, we can assume index is not
|
|
* out of bounds. */
|
|
static const size_t kIndex = 0;
|
|
if (!IsSubstrInRange(message_length, key_array[kIndex].key_id, true) ||
|
|
!IsSubstrInRange(message_length, key_array[kIndex].key_control, false) ||
|
|
!IsSubstrInRange(message_length, key_array[kIndex].key_control_iv,
|
|
true) ||
|
|
key_array[kIndex].key_id.length > KEY_ID_MAX_SIZE ||
|
|
key_array[kIndex].key_control.length < KEY_CONTROL_SIZE ||
|
|
(key_array[kIndex].key_control_iv.length != 0 &&
|
|
key_array[kIndex].key_control_iv.length != KEY_IV_SIZE)) {
|
|
LOGE("Invalid substring range for key: %zu", kIndex);
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
|
|
if (!session_context->refresh_valid) {
|
|
LOGE("Invalid key refresh");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
|
|
result = OPKI_VerifySignatureWithMacKeyServer(session_context, message,
|
|
message_length, signature);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to verify signature with server mac key, error: %u", result);
|
|
return result;
|
|
}
|
|
|
|
result = OEMCrypto_SUCCESS;
|
|
const uint8_t* key_id = NULL;
|
|
const uint8_t* key_control_iv = NULL;
|
|
if (key_array[kIndex].key_id.length != 0) {
|
|
key_id = message + key_array[kIndex].key_id.offset;
|
|
}
|
|
if (key_array[kIndex].key_control_iv.length != 0) {
|
|
key_control_iv = message + key_array[kIndex].key_control_iv.offset;
|
|
}
|
|
result = OPKI_RefreshKey(
|
|
session_context, key_id, key_array[kIndex].key_id.length,
|
|
message + key_array[kIndex].key_control.offset, key_control_iv);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to refresh key with result: %u", result);
|
|
return result;
|
|
}
|
|
return OPKI_SetStatePostCall(session_context, API_REFRESHKEYS);
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_QueryKeyControl(OEMCrypto_SESSION session,
|
|
const uint8_t* content_key_id,
|
|
size_t content_key_id_length,
|
|
uint8_t* key_control_block,
|
|
size_t* key_control_block_length) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
OEMCryptoSession* session_context = NULL;
|
|
OEMCryptoResult result = OPKI_GetSession(session, &session_context);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get session with result: %u, session = %u", result,
|
|
session);
|
|
return result;
|
|
}
|
|
ABORT_IF(session_context == NULL,
|
|
"OPKI_GetSession() provided invalid output.");
|
|
result = OPKI_CheckStatePreCall(session_context, API_QUERYKEYCONTROL);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
|
|
RETURN_INVALID_CONTEXT_IF_NULL(key_control_block_length);
|
|
|
|
if (*key_control_block_length < KEY_CONTROL_SIZE) {
|
|
*key_control_block_length = KEY_CONTROL_SIZE;
|
|
return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
}
|
|
|
|
RETURN_INVALID_CONTEXT_IF_NULL(content_key_id);
|
|
RETURN_INVALID_CONTEXT_IF_ZERO(content_key_id_length);
|
|
RETURN_INVALID_CONTEXT_IF_NULL(key_control_block);
|
|
if (content_key_id_length > KEY_ID_MAX_SIZE) {
|
|
LOGE("content_key_id_length is too large");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
|
|
*key_control_block_length = KEY_CONTROL_SIZE;
|
|
|
|
SymmetricKey* content_key = OPKI_FindKeyFromTable(
|
|
session_context, true, content_key_id, content_key_id_length);
|
|
if (content_key == NULL) return OEMCrypto_ERROR_NO_CONTENT_KEY;
|
|
|
|
KeyControlBlock control_block;
|
|
if (content_key->is_entitled_content_key) {
|
|
uint32_t entitlement_key_index = content_key->entitlement_key_index;
|
|
if (entitlement_key_index < session_context->num_entitlement_keys &&
|
|
OPKI_CheckKey(session_context->entitlement_keys[entitlement_key_index],
|
|
ENTITLEMENT_KEY)) {
|
|
control_block = session_context->entitlement_keys[entitlement_key_index]
|
|
->key_control_block;
|
|
} else {
|
|
LOGE("Invalid entitlement key, index = %u", entitlement_key_index);
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
} else {
|
|
control_block = content_key->key_control_block;
|
|
}
|
|
|
|
const uint32_t new_block[4] = {
|
|
0, /* Verification optional. */
|
|
oemcrypto_htobe32(control_block.duration),
|
|
0, /* Nonce optional. */
|
|
oemcrypto_htobe32(control_block.control_bits),
|
|
};
|
|
if (sizeof(new_block) != KEY_CONTROL_SIZE) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
memcpy(key_control_block, new_block, KEY_CONTROL_SIZE);
|
|
|
|
return OPKI_SetStatePostCall(session_context, API_QUERYKEYCONTROL);
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_SelectKey(OEMCrypto_SESSION session,
|
|
const uint8_t* content_key_id,
|
|
size_t content_key_id_length,
|
|
OEMCryptoCipherMode cipher_mode) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
OEMCryptoSession* session_context = NULL;
|
|
OEMCryptoResult result = OPKI_GetSession(session, &session_context);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get session with result: %u, session = %u", result,
|
|
session);
|
|
return result;
|
|
}
|
|
ABORT_IF(session_context == NULL,
|
|
"OPKI_GetSession() provided invalid output.");
|
|
result = OPKI_CheckStatePreCall(session_context, API_SELECTKEY);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
|
|
RETURN_INVALID_CONTEXT_IF_NULL(content_key_id);
|
|
RETURN_INVALID_CONTEXT_IF_ZERO(content_key_id_length);
|
|
if (content_key_id_length > KEY_ID_MAX_SIZE) {
|
|
LOGE("content_key_id_length is too large");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
if (cipher_mode != OEMCrypto_CipherMode_CTR &&
|
|
cipher_mode != OEMCrypto_CipherMode_CBC) {
|
|
LOGE("Unrecognized cipher_mode %u", (unsigned int)cipher_mode);
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
if (session_context->num_content_keys == 0) {
|
|
LOGE("Selected session has no content keys.");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
|
|
SymmetricKey* content_key = OPKI_FindKeyFromTable(
|
|
session_context, true, content_key_id, content_key_id_length);
|
|
if (!content_key) return OEMCrypto_ERROR_NO_CONTENT_KEY;
|
|
KeyControlBlock key_control_block;
|
|
if (content_key->is_entitled_content_key) {
|
|
if (session_context->license_type != OEMCrypto_EntitlementLicense ||
|
|
session_context->num_entitlement_keys == 0) {
|
|
LOGE("Not a entitlement license or no keys found");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
SymmetricKey* entitlement_key =
|
|
session_context->entitlement_keys[content_key->entitlement_key_index];
|
|
if (!OPKI_CheckKey(entitlement_key, ENTITLEMENT_KEY)) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
/* Key control block for entitled content key is controlled by its
|
|
entitlement key. */
|
|
key_control_block = entitlement_key->key_control_block;
|
|
} else {
|
|
if (session_context->license_type != OEMCrypto_ContentLicense) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
key_control_block = content_key->key_control_block;
|
|
}
|
|
if (WTPI_HasAnalogDisplay() && WTPI_SupportsCGMS_A() &&
|
|
(key_control_block.control_bits & CONTROL_OBSERVE_CGMS)) {
|
|
result = WTPI_ApplyCGMS(key_control_block.control_bits & CONTROL_CGMS_MASK);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
}
|
|
session_context->current_content_key_index = content_key->session_key_index;
|
|
content_key->cipher_mode = cipher_mode;
|
|
|
|
return OPKI_SetStatePostCall(session_context, API_SELECTKEY);
|
|
}
|
|
|
|
/* This function will only be called if at least some of the data is encrypted,
|
|
meaning this function can assume a key is required. */
|
|
static OEMCryptoResult DecryptCENCInternal(
|
|
OEMCrypto_SESSION session, const OEMCrypto_SampleDescription* samples,
|
|
size_t samples_length, const OEMCrypto_CENCEncryptPatternDesc* pattern) {
|
|
ABORT_IF(g_opk_system_state != SYSTEM_INITIALIZED,
|
|
"The system is uninitialized, and this was not caught at a higher "
|
|
"level.");
|
|
OEMCryptoSession* session_context = NULL;
|
|
OEMCryptoResult result = OPKI_GetSession(session, &session_context);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get session with result: %u, session = %u", result,
|
|
session);
|
|
return result;
|
|
}
|
|
ABORT_IF(session_context == NULL,
|
|
"OPKI_GetSession() provided invalid output.");
|
|
result = OPKI_CheckStatePreCall(session_context, API_DECRYPTCENC);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
RETURN_INVALID_CONTEXT_IF_NULL(samples);
|
|
RETURN_INVALID_CONTEXT_IF_ZERO(samples_length);
|
|
RETURN_INVALID_CONTEXT_IF_NULL(pattern);
|
|
|
|
const size_t max_length = WTPI_MaxSampleSize();
|
|
/* Iterate through all the samples and validate them before doing any decrypt.
|
|
*/
|
|
for (size_t sample_index = 0; sample_index < samples_length; ++sample_index) {
|
|
const OEMCrypto_SampleDescription* const sample = &(samples[sample_index]);
|
|
if (sample->buffers.input_data == NULL ||
|
|
sample->buffers.input_data_length == 0) {
|
|
LOGE("Sample %zu has invalid input data.", sample_index);
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
/* Iterate through all the subsamples and sum their lengths. */
|
|
size_t subsample_length_tally = 0;
|
|
for (size_t subsample_index = 0;
|
|
subsample_index < sample->subsamples_length; ++subsample_index) {
|
|
const OEMCrypto_SubSampleDescription* const subsample =
|
|
&(sample->subsamples[subsample_index]);
|
|
size_t length;
|
|
if (OPK_AddOverflowUX(subsample->num_bytes_clear,
|
|
subsample->num_bytes_encrypted, &length)) {
|
|
return OEMCrypto_ERROR_BUFFER_TOO_LARGE;
|
|
}
|
|
if (OPK_AddOverflowUX(subsample_length_tally, length,
|
|
&subsample_length_tally)) {
|
|
return OEMCrypto_ERROR_BUFFER_TOO_LARGE;
|
|
}
|
|
}
|
|
if (max_length != 0 && subsample_length_tally > max_length) {
|
|
return OEMCrypto_ERROR_BUFFER_TOO_LARGE;
|
|
}
|
|
if (subsample_length_tally != sample->buffers.input_data_length) {
|
|
LOGE("Sample %zu's length and subsample lengths do not match.",
|
|
sample_index);
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
}
|
|
|
|
result =
|
|
OPKI_DecryptSamples(session_context, samples, samples_length, pattern);
|
|
if (result == OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
/* OPKI_DecryptSamples may pass back an OEMCrypto_ERROR_SHORT_BUFFER that
|
|
originated in OPK_CheckOutputBounds(). This is not a valid return code
|
|
for DecryptCENC(), so we translate it here. */
|
|
LOGE("Output buffer size is too small");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
} else if (result != OEMCrypto_SUCCESS) {
|
|
return result;
|
|
}
|
|
|
|
return OPKI_SetStatePostCall(session_context, API_DECRYPTCENC);
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_DecryptCENC(
|
|
OEMCrypto_SESSION session, const OEMCrypto_SampleDescription* samples,
|
|
size_t samples_length, const OEMCrypto_CENCEncryptPatternDesc* pattern) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
|
|
RETURN_INVALID_CONTEXT_IF_NULL(samples);
|
|
RETURN_INVALID_CONTEXT_IF_ZERO(samples_length);
|
|
RETURN_INVALID_CONTEXT_IF_NULL(pattern);
|
|
/* DecryptCENC may be called for calls that could go through CopyBuffer. We
|
|
separate those calls out here so that the decrypt code path only has to
|
|
handle cases where a key is expected. */
|
|
bool has_encrypted_data = false;
|
|
for (size_t sample_index = 0; sample_index < samples_length; ++sample_index) {
|
|
if (samples[sample_index].subsamples_length != 1 ||
|
|
samples[sample_index].subsamples[0].num_bytes_encrypted > 0) {
|
|
has_encrypted_data = true;
|
|
break;
|
|
}
|
|
}
|
|
if (has_encrypted_data)
|
|
return DecryptCENCInternal(session, samples, samples_length, pattern);
|
|
|
|
const size_t max_length = WTPI_MaxSampleSize();
|
|
for (size_t sample_index = 0; sample_index < samples_length; ++sample_index) {
|
|
const OEMCrypto_SampleDescription* const sample = &(samples[sample_index]);
|
|
ABORT_IF(sample->subsamples_length != 1,
|
|
"Sample %zu with multiple subsamples was somehow not treated as "
|
|
"encrypted.",
|
|
sample_index);
|
|
const OEMCrypto_SubSampleDescription* const subsample =
|
|
&(sample->subsamples[0]);
|
|
if (max_length != 0 && subsample->num_bytes_clear > max_length) {
|
|
LOGE("Sample size too large: %zu bytes. Max allowed: %zu",
|
|
subsample->num_bytes_clear, max_length);
|
|
return OEMCrypto_ERROR_BUFFER_TOO_LARGE;
|
|
}
|
|
if (subsample->num_bytes_clear != sample->buffers.input_data_length) {
|
|
LOGE("Sample %zu's length and subsample length do not match.",
|
|
sample_index);
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
|
|
OEMCryptoResult result = OEMCrypto_CopyBuffer(
|
|
session, sample->buffers.input_data, sample->buffers.input_data_length,
|
|
&sample->buffers.output_descriptor, subsample->subsample_flags);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to copy buffer with result: %u", result);
|
|
return result;
|
|
}
|
|
}
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_CopyBuffer(
|
|
OEMCrypto_SESSION session UNUSED, const uint8_t* data_addr,
|
|
size_t data_length, const OEMCrypto_DestBufferDesc* dest_buffer_desc,
|
|
uint8_t subsample_flags UNUSED) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
/* We don't need to check the session, CopyBuffer should be allowed for all
|
|
session states since it doesn't access any state data. */
|
|
RETURN_INVALID_CONTEXT_IF_NULL(data_addr);
|
|
RETURN_INVALID_CONTEXT_IF_ZERO(data_length);
|
|
RETURN_INVALID_CONTEXT_IF_NULL(dest_buffer_desc);
|
|
OEMCryptoResult result;
|
|
|
|
OPK_OutputBuffer output_buffer;
|
|
size_t offset;
|
|
result = OPK_ParseDestBufferDesc(dest_buffer_desc, &output_buffer, &offset);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to parse dest buffer description with result: %u", result);
|
|
return result;
|
|
}
|
|
ABORT_IF(!OPK_IsOutputBufferValid(&output_buffer), "Invalid output buffer.");
|
|
result = OPK_CheckOutputBounds(&output_buffer, offset, data_length);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to check output bounds with result: %u", result);
|
|
return result;
|
|
}
|
|
return WTPI_C1_CopyToOutputBuffer(data_addr, data_length, &output_buffer,
|
|
offset);
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_Generic_Encrypt(
|
|
OEMCrypto_SESSION session, const uint8_t* in_buffer, size_t buffer_length,
|
|
const uint8_t* iv, OEMCrypto_Algorithm algorithm, uint8_t* out_buffer) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
OEMCryptoSession* session_context = NULL;
|
|
OEMCryptoResult result = OPKI_GetSession(session, &session_context);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get session with result: %u, session = %u", result,
|
|
session);
|
|
return result;
|
|
}
|
|
ABORT_IF(session_context == NULL,
|
|
"OPKI_GetSession() provided invalid output.");
|
|
result = OPKI_CheckStatePreCall(session_context, API_GENERICENCRYPT);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
|
|
RETURN_INVALID_CONTEXT_IF_NULL(in_buffer);
|
|
RETURN_INVALID_CONTEXT_IF_NULL(out_buffer);
|
|
RETURN_INVALID_CONTEXT_IF_ZERO(buffer_length);
|
|
RETURN_INVALID_CONTEXT_IF_NULL(iv);
|
|
if (algorithm != OEMCrypto_AES_CBC_128_NO_PADDING) {
|
|
LOGE("Invalid algorithm.");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
if (buffer_length % AES_BLOCK_SIZE != 0) {
|
|
LOGE("buffer_length of %zu is not a multiple of the crypto block length.",
|
|
buffer_length);
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
|
|
size_t max_buffer_size = WTPI_MaxBufferSizeForGenericCrypto();
|
|
if (max_buffer_size != 0 && buffer_length > max_buffer_size) {
|
|
return OEMCrypto_ERROR_BUFFER_TOO_LARGE;
|
|
}
|
|
if (session_context->current_content_key_index >=
|
|
session_context->num_content_keys) {
|
|
LOGE(
|
|
"Content key index out of range: index = %u, number of content keys: "
|
|
"%u",
|
|
session_context->current_content_key_index,
|
|
session_context->num_content_keys);
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
SymmetricKey* current_content_key =
|
|
session_context->content_keys[session_context->current_content_key_index];
|
|
if (current_content_key == NULL ||
|
|
current_content_key->key_size != KEY_SIZE_128) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
|
|
result = OPKI_CheckCurrentContentKeyUsage(
|
|
session_context, CONTROL_ALLOW_ENCRYPT, OPK_CLEAR_INSECURE_OUTPUT_BUFFER);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
result = OPKI_UpdatePlaybackTimeAndUsageEntryStatus(session_context);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
result = WTPI_C1_AESCBCEncrypt(current_content_key->key_handle, in_buffer,
|
|
buffer_length, iv, out_buffer);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to AES CBC encrypt with result: %u", result);
|
|
return result;
|
|
}
|
|
|
|
return OPKI_SetStatePostCall(session_context, API_GENERICENCRYPT);
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_Generic_Decrypt(
|
|
OEMCrypto_SESSION session, const uint8_t* in_buffer, size_t buffer_length,
|
|
const uint8_t* iv, OEMCrypto_Algorithm algorithm, uint8_t* out_buffer) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
OEMCryptoSession* session_context = NULL;
|
|
OEMCryptoResult result = OPKI_GetSession(session, &session_context);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get session with result: %u, session = %u", result,
|
|
session);
|
|
return result;
|
|
}
|
|
ABORT_IF(session_context == NULL,
|
|
"OPKI_GetSession() provided invalid output.");
|
|
result = OPKI_CheckStatePreCall(session_context, API_GENERICDECRYPT);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
|
|
RETURN_INVALID_CONTEXT_IF_NULL(in_buffer);
|
|
RETURN_INVALID_CONTEXT_IF_NULL(out_buffer);
|
|
RETURN_INVALID_CONTEXT_IF_ZERO(buffer_length);
|
|
RETURN_INVALID_CONTEXT_IF_NULL(iv);
|
|
if (algorithm != OEMCrypto_AES_CBC_128_NO_PADDING) {
|
|
LOGE("Invalid algorithm.");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
if (buffer_length % AES_BLOCK_SIZE != 0) {
|
|
LOGE("buffer_length of %zu is not a multiple of the crypto block length.",
|
|
buffer_length);
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
|
|
size_t max_buffer_size = WTPI_MaxBufferSizeForGenericCrypto();
|
|
if (max_buffer_size != 0 && buffer_length > max_buffer_size) {
|
|
return OEMCrypto_ERROR_BUFFER_TOO_LARGE;
|
|
}
|
|
if (session_context->current_content_key_index >=
|
|
session_context->num_content_keys) {
|
|
LOGE(
|
|
"Content key index out of range: index = %u, number of content keys: "
|
|
"%u",
|
|
session_context->current_content_key_index,
|
|
session_context->num_content_keys);
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
SymmetricKey* current_content_key =
|
|
session_context->content_keys[session_context->current_content_key_index];
|
|
if (current_content_key == NULL ||
|
|
current_content_key->key_size != KEY_SIZE_128) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
|
|
result = OPKI_CheckCurrentContentKeyUsage(
|
|
session_context, CONTROL_ALLOW_DECRYPT, OPK_CLEAR_INSECURE_OUTPUT_BUFFER);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
result = OPKI_UpdatePlaybackTimeAndUsageEntryStatus(session_context);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
KeySize key_size;
|
|
result = WTPI_K1_GetKeySize(current_content_key->key_handle, &key_size);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
result =
|
|
WTPI_C1_AESCBCDecrypt(current_content_key->key_handle, (size_t)key_size,
|
|
in_buffer, buffer_length, iv, out_buffer);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to AES CBC decrypt with result: %u", result);
|
|
return result;
|
|
}
|
|
|
|
return OPKI_SetStatePostCall(session_context, API_GENERICDECRYPT);
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_Generic_Sign(OEMCrypto_SESSION session,
|
|
const uint8_t* in_buffer,
|
|
size_t buffer_length,
|
|
OEMCrypto_Algorithm algorithm,
|
|
uint8_t* signature,
|
|
size_t* signature_length) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
OEMCryptoSession* session_context = NULL;
|
|
OEMCryptoResult result = OPKI_GetSession(session, &session_context);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get session with result: %u, session = %u", result,
|
|
session);
|
|
return result;
|
|
}
|
|
ABORT_IF(session_context == NULL,
|
|
"OPKI_GetSession() provided invalid output.");
|
|
result = OPKI_CheckStatePreCall(session_context, API_GENERICSIGN);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
|
|
RETURN_INVALID_CONTEXT_IF_NULL(signature_length);
|
|
|
|
if (*signature_length < SHA256_DIGEST_LENGTH) {
|
|
*signature_length = SHA256_DIGEST_LENGTH;
|
|
return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
}
|
|
|
|
RETURN_INVALID_CONTEXT_IF_NULL(in_buffer);
|
|
RETURN_INVALID_CONTEXT_IF_ZERO(buffer_length);
|
|
RETURN_INVALID_CONTEXT_IF_NULL(signature);
|
|
if (algorithm != OEMCrypto_HMAC_SHA256) {
|
|
LOGE("Invalid algorithm.");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
|
|
*signature_length = SHA256_DIGEST_LENGTH;
|
|
|
|
size_t max_buffer_size = WTPI_MaxBufferSizeForGenericCrypto();
|
|
if (max_buffer_size != 0 && buffer_length > max_buffer_size) {
|
|
return OEMCrypto_ERROR_BUFFER_TOO_LARGE;
|
|
}
|
|
if (session_context->current_content_key_index >=
|
|
session_context->num_content_keys) {
|
|
LOGE(
|
|
"Content key index out of range: index = %u, number of content keys: "
|
|
"%u",
|
|
session_context->current_content_key_index,
|
|
session_context->num_content_keys);
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
SymmetricKey* current_content_key =
|
|
session_context->content_keys[session_context->current_content_key_index];
|
|
/* Only allow 256-bit keys for signing and verifying. */
|
|
if (current_content_key == NULL ||
|
|
current_content_key->key_size != KEY_SIZE_256) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
|
|
result = OPKI_CheckCurrentContentKeyUsage(session_context, CONTROL_ALLOW_SIGN,
|
|
OPK_CLEAR_INSECURE_OUTPUT_BUFFER);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
result = OPKI_UpdatePlaybackTimeAndUsageEntryStatus(session_context);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
result = WTPI_C1_HMAC_SHA256(current_content_key->key_handle, in_buffer,
|
|
buffer_length, signature);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to HMAC-SHA256 with result: %u", result);
|
|
return result;
|
|
}
|
|
|
|
return OPKI_SetStatePostCall(session_context, API_GENERICSIGN);
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_Generic_Verify(OEMCrypto_SESSION session,
|
|
const uint8_t* in_buffer,
|
|
size_t buffer_length,
|
|
OEMCrypto_Algorithm algorithm,
|
|
const uint8_t* signature,
|
|
size_t signature_length) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
OEMCryptoSession* session_context = NULL;
|
|
OEMCryptoResult result = OPKI_GetSession(session, &session_context);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get session with result: %u, session = %u", result,
|
|
session);
|
|
return result;
|
|
}
|
|
ABORT_IF(session_context == NULL,
|
|
"OPKI_GetSession() provided invalid output.");
|
|
result = OPKI_CheckStatePreCall(session_context, API_GENERICVERIFY);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
|
|
RETURN_INVALID_CONTEXT_IF_NULL(in_buffer);
|
|
RETURN_INVALID_CONTEXT_IF_ZERO(buffer_length);
|
|
RETURN_INVALID_CONTEXT_IF_NULL(signature);
|
|
if (signature_length != SHA256_DIGEST_LENGTH) {
|
|
LOGE("signature_length is not the length of a SHA256 digest");
|
|
return OEMCrypto_ERROR_SIGNATURE_FAILURE;
|
|
}
|
|
if (algorithm != OEMCrypto_HMAC_SHA256) {
|
|
LOGE("Invalid algorithm.");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
|
|
size_t max_buffer_size = WTPI_MaxBufferSizeForGenericCrypto();
|
|
if (max_buffer_size != 0 && buffer_length > max_buffer_size) {
|
|
return OEMCrypto_ERROR_BUFFER_TOO_LARGE;
|
|
}
|
|
if (session_context->current_content_key_index >=
|
|
session_context->num_content_keys) {
|
|
LOGE(
|
|
"Content key index out of range: index = %u, number of content keys: "
|
|
"%u",
|
|
session_context->current_content_key_index,
|
|
session_context->num_content_keys);
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
SymmetricKey* current_content_key =
|
|
session_context->content_keys[session_context->current_content_key_index];
|
|
/* Only allow 256-bit keys for signing and verifying. */
|
|
if (current_content_key == NULL ||
|
|
current_content_key->key_size != KEY_SIZE_256) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
|
|
result = OPKI_CheckCurrentContentKeyUsage(
|
|
session_context, CONTROL_ALLOW_VERIFY, OPK_CLEAR_INSECURE_OUTPUT_BUFFER);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
result = OPKI_UpdatePlaybackTimeAndUsageEntryStatus(session_context);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
result = WTPI_C1_HMAC_SHA256_Verify(current_content_key->key_handle,
|
|
in_buffer, buffer_length, signature);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to HMAC-SHA256 verify with result: %u", result);
|
|
return result;
|
|
}
|
|
|
|
return OPKI_SetStatePostCall(session_context, API_GENERICVERIFY);
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_WrapKeyboxOrOEMCert(
|
|
const uint8_t* rot UNUSED, size_t rotLength UNUSED,
|
|
uint8_t* wrappedRot UNUSED, size_t* wrappedRotLength UNUSED,
|
|
const uint8_t* transportKey UNUSED, size_t transportKeyLength UNUSED) {
|
|
// TODO(fredgc, ncbray, marcone): One installation option is that there an
|
|
// installation tool that uses a system key to unwrap the keybox and then
|
|
// re-wraps it using a device key. This wrapped keybox can be stored on the
|
|
// file system. I think we don't plan to use this. If we do, then Widevine
|
|
// needs to implement this function, and we have to agree on an porting layer
|
|
// interface for:
|
|
// decrypt w/system key -> encrypt w/device key -> return buffer.
|
|
//
|
|
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_InstallKeyboxOrOEMCert(const uint8_t* keybox,
|
|
size_t length) {
|
|
// TODO(b/180530495): We currently only support keyboxes.
|
|
return WTPI_UnwrapValidateAndInstallKeybox(keybox, length);
|
|
}
|
|
|
|
OEMCrypto_ProvisioningMethod OEMCrypto_GetProvisioningMethod(void) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ProvisioningError;
|
|
}
|
|
return WTPI_GetProvisioningMethod();
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_IsKeyboxOrOEMCertValid(void) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
const OEMCrypto_ProvisioningMethod provisioning_method =
|
|
WTPI_GetProvisioningMethod();
|
|
if (provisioning_method == OEMCrypto_OEMCertificate) {
|
|
// TODO(b/180530495): Implement this.
|
|
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
|
} else if (provisioning_method == OEMCrypto_Keybox) {
|
|
return WTPI_ValidateKeybox();
|
|
} else {
|
|
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_GetDeviceID(uint8_t* deviceID, size_t* idLength) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
// GetDeviceID handles validation of the parameters, so we do not validate
|
|
// them here.
|
|
return GetDeviceID(deviceID, idLength);
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_GetKeyData(uint8_t* key_data,
|
|
size_t* key_data_length) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (WTPI_GetProvisioningMethod() != OEMCrypto_Keybox) {
|
|
LOGD("This function is only valid on Provisioning 2.0 devices");
|
|
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
RETURN_INVALID_CONTEXT_IF_NULL(key_data_length);
|
|
if (*key_data_length < KEYBOX_KEY_DATA_SIZE) {
|
|
*key_data_length = KEYBOX_KEY_DATA_SIZE;
|
|
return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
}
|
|
RETURN_INVALID_CONTEXT_IF_NULL(key_data);
|
|
*key_data_length = KEYBOX_KEY_DATA_SIZE;
|
|
return WTPI_GetKeyDataFromKeybox(key_data, *key_data_length);
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_LoadTestKeybox(const uint8_t* buffer, size_t length) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (WTPI_GetProvisioningMethod() != OEMCrypto_Keybox) {
|
|
LOGD("This function is only valid on Provisioning 2.0 devices");
|
|
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
RETURN_INVALID_CONTEXT_IF_NULL(buffer);
|
|
if (length < sizeof(WidevineKeybox)) {
|
|
LOGE("length too short");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
|
|
return WTPI_LoadTestKeybox(buffer, length);
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_GetOEMPublicCertificate(
|
|
uint8_t* public_cert UNUSED, size_t* public_cert_length UNUSED) {
|
|
// TODO(b/180530495): implement this.
|
|
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_LoadOEMPrivateKey(OEMCrypto_SESSION session) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (WTPI_GetProvisioningMethod() != OEMCrypto_OEMCertificate) {
|
|
LOGD("This function is only valid on Provisioning 3.0 devices");
|
|
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
OEMCryptoSession* session_context = NULL;
|
|
OEMCryptoResult result = OPKI_GetSession(session, &session_context);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get session with result: %u, session = %u", result,
|
|
session);
|
|
return result;
|
|
}
|
|
ABORT_IF(session_context == NULL,
|
|
"OPKI_GetSession() provided invalid output.");
|
|
result = OPKI_CheckStatePreCall(session_context, API_LOADOEMPRIVATEKEY);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
/* This API sets the session state to indicate that the OEM private key is
|
|
* ready for use. It doesn't actually load it. It is best to load the OEM
|
|
* private key in memory only when being used. */
|
|
return OPKI_SetStatePostCall(session_context, API_LOADOEMPRIVATEKEY);
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_GetRandom(uint8_t* randomData, size_t dataLength) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
RETURN_INVALID_CONTEXT_IF_NULL(randomData);
|
|
RETURN_INVALID_CONTEXT_IF_ZERO(dataLength);
|
|
return WTPI_C1_RandomBytes(randomData, dataLength);
|
|
}
|
|
|
|
uint32_t OEMCrypto_APIVersion(void) { return API_MAJOR_VERSION; }
|
|
|
|
uint32_t OEMCrypto_MinorAPIVersion(void) { return API_MINOR_VERSION; }
|
|
|
|
const char* OEMCrypto_BuildInformation(void) { return BUILD_INFO(); }
|
|
|
|
uint8_t OEMCrypto_Security_Patch_Level(void) { return 0; }
|
|
|
|
const char* OEMCrypto_SecurityLevel(void) {
|
|
switch (WTPI_GetSecurityLevel()) {
|
|
case OPK_SECURITY_LEVEL_1:
|
|
return "L1";
|
|
case OPK_SECURITY_LEVEL_3:
|
|
return "L3";
|
|
}
|
|
ABORT("Impossible security level.");
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_GetHDCPCapability(
|
|
OEMCrypto_HDCP_Capability* current, OEMCrypto_HDCP_Capability* maximum) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
RETURN_INVALID_CONTEXT_IF_NULL(current);
|
|
RETURN_INVALID_CONTEXT_IF_NULL(maximum);
|
|
|
|
*current = WTPI_CurrentHDCPCapability();
|
|
*maximum = WTPI_MaxHDCPCapability();
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
bool OEMCrypto_SupportsUsageTable(void) { return true; }
|
|
|
|
size_t OEMCrypto_MaximumUsageTableHeaderSize(void) {
|
|
return MAX_NUMBER_OF_USAGE_ENTRIES;
|
|
}
|
|
|
|
bool OEMCrypto_IsAntiRollbackHwPresent(void) {
|
|
return WTPI_IsAntiRollbackHWPresent();
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_GetNumberOfOpenSessions(size_t* count) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
RETURN_INVALID_CONTEXT_IF_NULL(count);
|
|
|
|
uint32_t num_open_sessions = 0;
|
|
OEMCryptoResult result = OPKI_NumberOfOpenSessions(&num_open_sessions);
|
|
*count = num_open_sessions;
|
|
return result;
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_GetMaxNumberOfSessions(size_t* max) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
RETURN_INVALID_CONTEXT_IF_NULL(max);
|
|
|
|
*max = OPKI_MaxNumberOfSessions();
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
uint32_t OEMCrypto_SupportedCertificates(void) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return 0;
|
|
}
|
|
return WTPI_SupportedCertificates();
|
|
}
|
|
|
|
bool OEMCrypto_IsSRMUpdateSupported(void) { return false; }
|
|
|
|
OEMCryptoResult OEMCrypto_GetCurrentSRMVersion(uint16_t* version) {
|
|
RETURN_INVALID_CONTEXT_IF_NULL(version);
|
|
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
uint32_t OEMCrypto_GetAnalogOutputFlags(void) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return 0;
|
|
}
|
|
uint32_t analog_bits = 0;
|
|
if (WTPI_HasAnalogDisplay()) analog_bits |= 0x1;
|
|
if (WTPI_CanDisableAnalogDisplay()) analog_bits |= 0x2;
|
|
if (WTPI_SupportsCGMS_A()) analog_bits |= 0x4;
|
|
return analog_bits;
|
|
}
|
|
|
|
uint32_t OEMCrypto_ResourceRatingTier(void) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return 0;
|
|
}
|
|
return WTPI_GetResourceRatingTier();
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_LoadDRMPrivateKey(OEMCrypto_SESSION session,
|
|
OEMCrypto_PrivateKeyType key_type,
|
|
const uint8_t* wrapped_drm_key,
|
|
size_t wrapped_drm_key_length) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
AsymmetricKeyType drm_key_type;
|
|
if (!OPKI_PrivateKeyTypeToAsymmetricKey(key_type, &drm_key_type)) {
|
|
LOGE("Invalid key_type: %d", key_type);
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
OEMCryptoSession* session_context = NULL;
|
|
OEMCryptoResult result = OPKI_GetSession(session, &session_context);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get session with result: %u, session = %u", result,
|
|
session);
|
|
return result;
|
|
}
|
|
ABORT_IF(session_context == NULL,
|
|
"OPKI_GetSession() provided invalid output.");
|
|
result = OPKI_CheckStatePreCall(session_context, API_LOADDRMPRIVATEKEY);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
|
|
WTPI_AsymmetricKey_Handle private_key_handle;
|
|
uint32_t allowed_schemes;
|
|
result = WTPI_UnwrapIntoAsymmetricKeyHandle(
|
|
wrapped_drm_key, wrapped_drm_key_length, drm_key_type,
|
|
&private_key_handle, &allowed_schemes);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to unwrap DRM private key into key handle with result: %u",
|
|
result);
|
|
return result;
|
|
}
|
|
size_t private_key_size;
|
|
result = WTPI_GetSignatureSize(private_key_handle, &private_key_size);
|
|
WTPI_FreeAsymmetricKeyHandle(private_key_handle);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get DRM private key size with result: %u", result);
|
|
return result;
|
|
}
|
|
|
|
result = OPKI_LoadDRMKey(session_context, drm_key_type, wrapped_drm_key,
|
|
wrapped_drm_key_length, private_key_size,
|
|
allowed_schemes);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to load DRM key");
|
|
goto cleanup;
|
|
}
|
|
result = OPKI_SetStatePostCall(session_context, API_LOADDRMPRIVATEKEY);
|
|
|
|
cleanup:;
|
|
OEMCryptoResult free_key_result = FreeMacAndEncryptionKeys(session_context);
|
|
if (result == OEMCrypto_SUCCESS) result = free_key_result;
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
OPKI_FreeAsymmetricKeyFromTable(&session_context->drm_private_key);
|
|
session_context->state = SESSION_INVALID;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_LoadTestRSAKey(void) {
|
|
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_GenerateRSASignature(
|
|
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
|
|
uint8_t* signature, size_t* signature_length,
|
|
RSA_Padding_Scheme padding_scheme) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
OEMCryptoSession* session_context = NULL;
|
|
OEMCryptoResult result = OPKI_GetSession(session, &session_context);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get session with result: %u, session = %u", result,
|
|
session);
|
|
return result;
|
|
}
|
|
ABORT_IF(session_context == NULL,
|
|
"OPKI_GetSession() provided invalid output.");
|
|
result = OPKI_CheckStatePreCall(session_context, API_GENERATERSASIGNATURE);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
|
|
RETURN_INVALID_CONTEXT_IF_NULL(message);
|
|
RETURN_INVALID_CONTEXT_IF_ZERO(message_length);
|
|
RETURN_INVALID_CONTEXT_IF_NULL(signature_length);
|
|
|
|
if (padding_scheme == kSign_PKCS1_Block1 && message_length > 83) {
|
|
LOGE("message_length is too long for the given padding_scheme.");
|
|
return OEMCrypto_ERROR_SIGNATURE_FAILURE;
|
|
}
|
|
|
|
if (session_context->state == SESSION_LOAD_OEM_RSA_KEY &&
|
|
padding_scheme == kSign_RSASSA_PSS) {
|
|
// SignMessageWithOEMPrivateKey handles signature being NULL, so we
|
|
// intentionally do not check it here.
|
|
result = OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
|
// TODO(b/180530495): implement this.
|
|
// SignMessageWithOEMPrivateKey(message, message_length, signature,
|
|
// signature_length);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
} else if (session_context->state == SESSION_LOAD_DRM_RSA_KEY) {
|
|
if ((session_context->allowed_schemes & padding_scheme) != padding_scheme) {
|
|
LOGE("Invalid padding scheme: %u", session_context->allowed_schemes);
|
|
return OEMCrypto_ERROR_INVALID_RSA_KEY;
|
|
}
|
|
WTPI_AsymmetricKey_Handle private_key_handle;
|
|
uint32_t allowed_schemes;
|
|
AsymmetricKey* private_key = session_context->drm_private_key;
|
|
result = WTPI_UnwrapIntoAsymmetricKeyHandle(
|
|
private_key->wrapped_key, private_key->wrapped_key_length,
|
|
private_key->key_type, &private_key_handle, &allowed_schemes);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to unwrap DRM private key into key handle with result: %u",
|
|
result);
|
|
return result;
|
|
}
|
|
// WTPI_RSASign handles signature being NULL, so we intentionally do not
|
|
// check it here.
|
|
result = WTPI_RSASign(private_key_handle, message, message_length,
|
|
signature, signature_length, padding_scheme);
|
|
WTPI_FreeAsymmetricKeyHandle(private_key_handle);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to generate RSA signature with result: %u", result);
|
|
return result;
|
|
}
|
|
} else {
|
|
LOGE(
|
|
"Invalid session state or padding scheme. session state: %#x, padding "
|
|
"scheme: %u",
|
|
session_context->state, padding_scheme);
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
|
|
return OPKI_SetStatePostCall(session_context, API_GENERATERSASIGNATURE);
|
|
}
|
|
|
|
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 (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
OEMCryptoSession* session_context = NULL;
|
|
OEMCryptoResult result = OPKI_GetSession(session, &session_context);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get session with result: %u, session = %u", result,
|
|
session);
|
|
return result;
|
|
}
|
|
ABORT_IF(session_context == NULL,
|
|
"OPKI_GetSession() provided invalid output.");
|
|
result = OPKI_CheckStatePreCall(session_context, API_LOADPROVISIONING);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
|
|
RETURN_INVALID_CONTEXT_IF_NULL(message);
|
|
RETURN_INVALID_CONTEXT_IF_ZERO(message_length);
|
|
RETURN_INVALID_CONTEXT_IF_NULL(signature);
|
|
RETURN_INVALID_CONTEXT_IF_ZERO(signature_length);
|
|
if (signature_length != SHA256_DIGEST_LENGTH) {
|
|
LOGE("signature_length is not the length of a SHA256 digest");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
RETURN_INVALID_CONTEXT_IF_NULL(wrapped_private_key_length);
|
|
|
|
uint8_t device_id[ODK_DEVICE_ID_LEN_MAX] = {0};
|
|
size_t device_id_length = ODK_DEVICE_ID_LEN_MAX;
|
|
result = GetDeviceID(device_id, &device_id_length);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get device id with result: %u", result);
|
|
return result;
|
|
}
|
|
ODK_ParsedProvisioning parsed_response;
|
|
result = ODK_ParseProvisioning(message, message_length, core_message_length,
|
|
&session_context->nonce_values, device_id,
|
|
device_id_length, &parsed_response);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to parse provisioning with result: %u", result);
|
|
return result;
|
|
}
|
|
|
|
AsymmetricKeyType drm_key_type;
|
|
if (!OPKI_PrivateKeyTypeToAsymmetricKey(parsed_response.key_type,
|
|
&drm_key_type)) {
|
|
LOGE("Unexpected key type: %#x", parsed_response.key_type);
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (parsed_response.enc_private_key.length > PKCS8_DRM_KEY_MAX_SIZE) {
|
|
LOGE("Encrypted private key length is too large to handle.");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
if (parsed_response.enc_private_key_iv.length != KEY_IV_SIZE) {
|
|
LOGE("Encrypted private key iv has invalid length: %zu",
|
|
parsed_response.enc_private_key_iv.length);
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
|
|
size_t buffer_size = 0;
|
|
result = WTPI_GetWrappedAsymmetricKeySize(
|
|
parsed_response.enc_private_key.length, drm_key_type, &buffer_size);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get wrapped DRM key size with result: %u", result);
|
|
return result;
|
|
}
|
|
ABORT_IF_ZERO(buffer_size);
|
|
if (wrapped_private_key == NULL ||
|
|
*wrapped_private_key_length < buffer_size) {
|
|
*wrapped_private_key_length = buffer_size;
|
|
return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
}
|
|
/* Tell caller how much space we used. */
|
|
*wrapped_private_key_length = buffer_size;
|
|
|
|
const uint8_t* message_body = message + core_message_length;
|
|
const OEMCrypto_ProvisioningMethod provisioning_method =
|
|
WTPI_GetProvisioningMethod();
|
|
if (provisioning_method == OEMCrypto_Keybox) {
|
|
result = RewrapDeviceDRMKeyKeybox(
|
|
session_context, message, message_length, signature, signature_length,
|
|
message_body + parsed_response.enc_private_key.offset,
|
|
parsed_response.enc_private_key.length,
|
|
message_body + parsed_response.enc_private_key_iv.offset, drm_key_type,
|
|
wrapped_private_key, *wrapped_private_key_length);
|
|
} else if (provisioning_method == OEMCrypto_OEMCertificate) {
|
|
result = RewrapDeviceDRMKeyOEMCert(
|
|
session_context,
|
|
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, drm_key_type,
|
|
wrapped_private_key, *wrapped_private_key_length);
|
|
} else {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
return OPKI_SetStatePostCall(session_context, API_LOADPROVISIONING);
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_CreateUsageTableHeader(uint8_t* header_buffer,
|
|
size_t* header_buffer_length) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
// OPKI_CreateUsageTableHeader handles validation of the parameters, so we do
|
|
// not validate them here.
|
|
return OPKI_CreateUsageTableHeader(header_buffer, header_buffer_length);
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_LoadUsageTableHeader(const uint8_t* header_buffer,
|
|
size_t header_buffer_length) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
// OPKI_LoadUsageTableHeader handles validation of the parameters, so we do
|
|
// not validate them here.
|
|
return OPKI_LoadUsageTableHeader(header_buffer, header_buffer_length);
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_CreateNewUsageEntry(OEMCrypto_SESSION session,
|
|
uint32_t* usage_entry_number) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
OEMCryptoSession* session_context = NULL;
|
|
OEMCryptoResult result = OPKI_GetSession(session, &session_context);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get session with result: %u, session = %u", result,
|
|
session);
|
|
return result;
|
|
}
|
|
ABORT_IF(session_context == NULL,
|
|
"OPKI_GetSession() provided invalid output.");
|
|
result = OPKI_CheckStatePreCall(session_context, API_CREATENEWUSAGEENTRY);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
|
|
// OPKI_CreateNewUsageEntry handles validation of the parameters, so we do not
|
|
// validate them here.
|
|
result =
|
|
OPKI_CreateNewUsageEntry(session_context->session_id, usage_entry_number);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to create new usage entry with result: %u, session_id = %u",
|
|
result, session_context->session_id);
|
|
return result;
|
|
}
|
|
return OPKI_SetStatePostCall(session_context, API_CREATENEWUSAGEENTRY);
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_LoadUsageEntry(OEMCrypto_SESSION session,
|
|
uint32_t usage_entry_number,
|
|
const uint8_t* buffer,
|
|
size_t buffer_length) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
RETURN_INVALID_CONTEXT_IF_NULL(buffer);
|
|
RETURN_INVALID_CONTEXT_IF_ZERO(buffer_length);
|
|
|
|
OEMCryptoSession* session_context = NULL;
|
|
OEMCryptoResult result = OPKI_GetSession(session, &session_context);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get session with result: %u, session = %u", result,
|
|
session);
|
|
return result;
|
|
}
|
|
ABORT_IF(session_context == NULL,
|
|
"OPKI_GetSession() provided invalid output.");
|
|
result = OPKI_CheckStatePreCall(session_context, API_LOADUSAGEENTRY);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
|
|
result = OPKI_LoadUsageEntry(session_context->session_id,
|
|
&session_context->clock_values,
|
|
usage_entry_number, buffer, buffer_length);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to load usage entry with result: %u, session_id = %u", result,
|
|
session_context->session_id);
|
|
return result;
|
|
}
|
|
return OPKI_SetStatePostCall(session_context, API_LOADUSAGEENTRY);
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_UpdateUsageEntry(OEMCrypto_SESSION session,
|
|
uint8_t* header_buffer,
|
|
size_t* header_buffer_length,
|
|
uint8_t* entry_buffer,
|
|
size_t* entry_buffer_length) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
OEMCryptoSession* session_context = NULL;
|
|
OEMCryptoResult result = OPKI_GetSession(session, &session_context);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get session with result: %u, session = %u", result,
|
|
session);
|
|
return result;
|
|
}
|
|
ABORT_IF(session_context == NULL,
|
|
"OPKI_GetSession() provided invalid output.");
|
|
result = OPKI_CheckStatePreCall(session_context, API_UPDATEUSAGEENTRY);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
|
|
// OPKI_UpdateUsageEntry handles validation of the parameters, so we do not
|
|
// validate them here.
|
|
result = OPKI_UpdateUsageEntry(
|
|
session_context->session_id, &session_context->clock_values,
|
|
header_buffer, header_buffer_length, entry_buffer, entry_buffer_length);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
if (result != OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
LOGE("Failed to update usage entry with result: %u, session_id = %u",
|
|
result, session_context->session_id);
|
|
}
|
|
return result;
|
|
}
|
|
return OPKI_SetStatePostCall(session_context, API_UPDATEUSAGEENTRY);
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_DeactivateUsageEntry(OEMCrypto_SESSION session,
|
|
const uint8_t* pst,
|
|
size_t pst_length) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
OEMCryptoSession* session_context = NULL;
|
|
OEMCryptoResult result = OPKI_GetSession(session, &session_context);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get session with result: %u, session = %u", result,
|
|
session);
|
|
return result;
|
|
}
|
|
ABORT_IF(session_context == NULL,
|
|
"OPKI_GetSession() provided invalid output.");
|
|
result = OPKI_CheckStatePreCall(session_context, API_DEACTIVATEUSAGEENTRY);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
|
|
// OPKI_DeactivateUsageEntry handles validation of the parameters, so we do
|
|
// not validate them here.
|
|
result = OPKI_DeactivateUsageEntry(session_context->session_id,
|
|
&session_context->clock_values, pst,
|
|
pst_length);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to deactivate usage entry with result: %u, session_id = %u",
|
|
result, session_context->session_id);
|
|
return result;
|
|
}
|
|
return OPKI_SetStatePostCall(session_context, API_DEACTIVATEUSAGEENTRY);
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_ReportUsage(OEMCrypto_SESSION session,
|
|
const uint8_t* pst, size_t pst_length,
|
|
uint8_t* buffer, size_t* buffer_length) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
OEMCryptoSession* session_context = NULL;
|
|
OEMCryptoResult result = OPKI_GetSession(session, &session_context);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get session with result: %u, session = %u", result,
|
|
session);
|
|
return result;
|
|
}
|
|
ABORT_IF(session_context == NULL,
|
|
"OPKI_GetSession() provided invalid output.");
|
|
result = OPKI_CheckStatePreCall(session_context, API_REPORTUSAGE);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
|
|
WTPI_K1_SymmetricKey_Handle mac_key_client =
|
|
OPKI_CheckKey(session_context->mac_key_client, MAC_KEY_CLIENT)
|
|
? session_context->mac_key_client->key_handle
|
|
: NULL;
|
|
// OPKI_ReportUsage handles validation of the parameters, so we do not
|
|
// validate them here.
|
|
result = OPKI_ReportUsage(session_context->session_id, mac_key_client, pst,
|
|
pst_length, buffer, buffer_length);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
return OPKI_SetStatePostCall(session_context, API_REPORTUSAGE);
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_MoveEntry(OEMCrypto_SESSION session,
|
|
uint32_t new_index) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
OEMCryptoSession* session_context = NULL;
|
|
OEMCryptoResult result = OPKI_GetSession(session, &session_context);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get session with result: %u, session = %u", result,
|
|
session);
|
|
return result;
|
|
}
|
|
ABORT_IF(session_context == NULL,
|
|
"OPKI_GetSession() provided invalid output.");
|
|
result = OPKI_CheckStatePreCall(session_context, API_MOVEENTRY);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
|
|
result = OPKI_MoveEntry(session_context->session_id, new_index);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE(
|
|
"Failed to move entry with result: %u, session_id = %u, new_index = %u",
|
|
result, session_context->session_id, new_index);
|
|
return result;
|
|
}
|
|
return OPKI_SetStatePostCall(session_context, API_MOVEENTRY);
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_ShrinkUsageTableHeader(uint32_t new_entry_count,
|
|
uint8_t* header_buffer,
|
|
size_t* header_buffer_length) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
// OPKI_ShrinkUsageTableHeader handles validation of the parameters, so we do
|
|
// not validate them here.
|
|
return OPKI_ShrinkUsageTableHeader(new_entry_count, header_buffer,
|
|
header_buffer_length);
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_RemoveSRM(void) {
|
|
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
uint32_t OEMCrypto_SupportsDecryptHash(void) {
|
|
return OEMCrypto_CRC_Clear_Buffer;
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_SetDecryptHash(OEMCrypto_SESSION session,
|
|
uint32_t frame_number,
|
|
const uint8_t* hash,
|
|
size_t hash_length) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
OEMCryptoSession* session_context = NULL;
|
|
OEMCryptoResult result = OPKI_GetSession(session, &session_context);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get session with result: %u, session = %u", result,
|
|
session);
|
|
return result;
|
|
}
|
|
ABORT_IF(session_context == NULL,
|
|
"OPKI_GetSession() provided invalid output.");
|
|
result = OPKI_CheckStatePreCall(session_context, API_SETDECRYPTHASH);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
|
|
RETURN_INVALID_CONTEXT_IF_NULL(hash);
|
|
const size_t expected_hash_length = sizeof(session_context->given_hash);
|
|
if (hash_length < expected_hash_length) return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
if (hash_length > expected_hash_length) {
|
|
LOGE("Bad hash_length: %zu, expected: %zu", hash_length,
|
|
expected_hash_length);
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
|
|
session_context->compute_hash = true;
|
|
session_context->current_frame_number = frame_number;
|
|
/* This is safe since it's host-to-host. */
|
|
memcpy(&session_context->given_hash, hash, hash_length);
|
|
|
|
return OPKI_SetStatePostCall(session_context, API_SETDECRYPTHASH);
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_GetHashErrorCode(OEMCrypto_SESSION session,
|
|
uint32_t* failed_frame_number) {
|
|
if (g_opk_system_state != SYSTEM_INITIALIZED) {
|
|
LOGE("OEMCrypto is not yet initialized");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
OEMCryptoSession* session_context = NULL;
|
|
OEMCryptoResult result = OPKI_GetSession(session, &session_context);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to get session with result: %u, session = %u", result,
|
|
session);
|
|
return result;
|
|
}
|
|
ABORT_IF(session_context == NULL,
|
|
"OPKI_GetSession() provided invalid output.");
|
|
result = OPKI_CheckStatePreCall(session_context, API_GETHASHERRORCODE);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
|
|
RETURN_INVALID_CONTEXT_IF_NULL(failed_frame_number);
|
|
if (session_context->hash_error != OEMCrypto_SUCCESS) {
|
|
*failed_frame_number = session_context->bad_frame_number;
|
|
}
|
|
|
|
result = OPKI_SetStatePostCall(session_context, API_GETHASHERRORCODE);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
return session_context->hash_error;
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_AllocateSecureBuffer(
|
|
OEMCrypto_SESSION session UNUSED, size_t buffer_size,
|
|
OEMCrypto_DestBufferDesc* output_descriptor, int* secure_fd) {
|
|
RETURN_INVALID_CONTEXT_IF_ZERO(buffer_size);
|
|
RETURN_INVALID_CONTEXT_IF_NULL(output_descriptor);
|
|
RETURN_INVALID_CONTEXT_IF_NULL(secure_fd);
|
|
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_FreeSecureBuffer(
|
|
OEMCrypto_SESSION session UNUSED,
|
|
OEMCrypto_DestBufferDesc* output_descriptor, int secure_fd UNUSED) {
|
|
RETURN_INVALID_CONTEXT_IF_NULL(output_descriptor);
|
|
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_GenerateOTARequest(OEMCrypto_SESSION session UNUSED,
|
|
uint8_t* buffer UNUSED,
|
|
size_t* buffer_length,
|
|
uint32_t use_test_key UNUSED) {
|
|
RETURN_INVALID_CONTEXT_IF_NULL(buffer_length);
|
|
/* Implementing this method is optional. It is only applicable
|
|
* a specific set of devices. */
|
|
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
OEMCryptoResult OEMCrypto_ProcessOTAKeybox(OEMCrypto_SESSION session UNUSED,
|
|
const uint8_t* buffer,
|
|
size_t buffer_length,
|
|
uint32_t use_test_key UNUSED) {
|
|
RETURN_INVALID_CONTEXT_IF_NULL(buffer);
|
|
RETURN_INVALID_CONTEXT_IF_ZERO(buffer_length);
|
|
/* Implementing this method is optional. It is only applicable
|
|
* a specific set of devices. */
|
|
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
|
}
|