This is a patch release that fixes a bug in the OPK where calling OEMCrypto_MoveEntry() immediately after calling OEMCrypto_CreateNewUsageEntry() returns an error when it should return success.
1413 lines
52 KiB
C
1413 lines
52 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 "oemcrypto_session.h"
|
|
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
|
|
#include "odk.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_control_block.h"
|
|
#include "oemcrypto_key_table.h"
|
|
#include "oemcrypto_math.h"
|
|
#include "oemcrypto_output.h"
|
|
#include "oemcrypto_overflow.h"
|
|
#include "oemcrypto_session_key_table.h"
|
|
#include "oemcrypto_usage_table.h"
|
|
#include "wtpi_abort_interface.h"
|
|
#include "wtpi_clock_interface_layer1.h"
|
|
#include "wtpi_crc32_interface.h"
|
|
#include "wtpi_decrypt_sample_interface.h"
|
|
#include "wtpi_logging_interface.h"
|
|
#include "wtpi_root_of_trust_interface_layer1.h"
|
|
|
|
NO_IGNORE_RESULT static bool IsSupportedDrmKeyType(AsymmetricKeyType key_type) {
|
|
return key_type == DRM_RSA_PRIVATE_KEY || key_type == DRM_ECC_PRIVATE_KEY;
|
|
}
|
|
|
|
/* In OEMCrypto version 16.5 and forward, key control blocks are expected to be
|
|
* clear. Caller ensures the pointer is not NULL. */
|
|
NO_IGNORE_RESULT static bool IsExpectedClearKeyControlBlockVersion(
|
|
const ODK_NonceValues* nonce_values) {
|
|
if (nonce_values->api_major_version == 16) {
|
|
return nonce_values->api_minor_version >= 5;
|
|
}
|
|
return nonce_values->api_major_version >= 17;
|
|
}
|
|
|
|
OEMCryptoResult OPKI_InitializeSession(OEMCryptoSession* session,
|
|
OEMCrypto_SESSION session_id) {
|
|
RETURN_INVALID_CONTEXT_IF_NULL(session);
|
|
*session = (OEMCryptoSession){
|
|
.session_id = session_id,
|
|
.state = SESSION_INVALID,
|
|
.license_type = OEMCrypto_ContentLicense,
|
|
.current_content_key_index = CONTENT_KEYS_PER_SESSION,
|
|
.decrypt_hash =
|
|
(DecryptHash){
|
|
.hash_error = OEMCrypto_SUCCESS,
|
|
},
|
|
.allowed_schemes = kSign_RSASSA_PSS,
|
|
.prov40_oem_allowed_schemes = kSign_RSASSA_PSS,
|
|
};
|
|
OEMCryptoResult result = ODK_InitializeSessionValues(
|
|
&session->timer_limits, &session->clock_values, &session->nonce_values,
|
|
API_MAJOR_VERSION, session->session_id);
|
|
return result;
|
|
}
|
|
|
|
OEMCryptoResult OPKI_TerminateSession(OEMCryptoSession* session) {
|
|
if (session == NULL) return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
OEMCryptoResult result = OEMCrypto_SUCCESS;
|
|
|
|
result = OPKI_FreeAsymmetricKeyFromTable(&session->drm_private_key);
|
|
|
|
OEMCryptoResult free_key_result =
|
|
OPKI_FreeKeyFromTable(&session->mac_key_client);
|
|
if (result == OEMCrypto_SUCCESS) result = free_key_result;
|
|
|
|
free_key_result = OPKI_FreeKeyFromTable(&session->mac_key_server);
|
|
if (result == OEMCrypto_SUCCESS) result = free_key_result;
|
|
|
|
free_key_result = OPKI_FreeKeyFromTable(&session->encryption_key);
|
|
if (result == OEMCrypto_SUCCESS) result = free_key_result;
|
|
|
|
for (uint32_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 (uint32_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;
|
|
}
|
|
|
|
OPKI_ReleaseEntry(session->session_id);
|
|
memset(session, 0, sizeof(OEMCryptoSession));
|
|
return result;
|
|
}
|
|
|
|
/* State definitions and transitions are described in section: "Clarify
|
|
* OEMCrypto States and Remove Unused Functions" in
|
|
* <$CDM_DIR>/doc/Widevine_Modular_DRM_Version_16_Delta.pdf */
|
|
OEMCryptoResult OPKI_CheckStatePreCall(OEMCryptoSession* session,
|
|
OEMCryptoSessionAPI api) {
|
|
if (session == NULL) return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
switch (api) {
|
|
case API_OPENSESSION:
|
|
switch (session->state) {
|
|
case SESSION_INVALID:
|
|
return OEMCrypto_SUCCESS;
|
|
default:
|
|
goto err;
|
|
}
|
|
case API_GENERATENONCE:
|
|
switch (session->state) {
|
|
case (SESSION_OPENED):
|
|
case (SESSION_LOAD_DRM_RSA_KEY):
|
|
return OEMCrypto_SUCCESS;
|
|
default:
|
|
goto err;
|
|
}
|
|
case API_GENERATEDERIVEDKEYS:
|
|
switch (session->state) {
|
|
case (SESSION_OPENED):
|
|
case (SESSION_WAIT_FOR_PROVISIONING):
|
|
case (SESSION_PREPARING_REQUEST):
|
|
return OEMCrypto_SUCCESS;
|
|
default:
|
|
goto err;
|
|
}
|
|
case API_DERIVEKEYSFROMSESSIONKEY:
|
|
switch (session->state) {
|
|
case (SESSION_WAIT_FOR_PROVISIONING):
|
|
case (SESSION_WAIT_FOR_LICENSE):
|
|
case (SESSION_LOAD_DRM_RSA_KEY):
|
|
case (SESSION_USAGE_ENTRY_LOADED):
|
|
return OEMCrypto_SUCCESS;
|
|
default:
|
|
goto err;
|
|
}
|
|
case API_PREPANDSIGN_LICENSE_REQUEST:
|
|
switch (session->state) {
|
|
case (SESSION_PREPARING_REQUEST):
|
|
case (SESSION_LOAD_DRM_RSA_KEY):
|
|
return OEMCrypto_SUCCESS;
|
|
default:
|
|
goto err;
|
|
}
|
|
case API_PREPANDSIGN_PROVISION_REQUEST:
|
|
switch (session->state) {
|
|
case (SESSION_PREPARING_REQUEST):
|
|
case (SESSION_LOAD_OEM_RSA_KEY):
|
|
return OEMCrypto_SUCCESS;
|
|
default:
|
|
goto err;
|
|
}
|
|
case API_PREPANDSIGN_RENEWAL_REQUEST:
|
|
switch (session->state) {
|
|
case (SESSION_LICENSE_LOADED):
|
|
case (SESSION_EXPIRED):
|
|
case (SESSION_PLAYING):
|
|
case (SESSION_USAGE_ENTRY_LOADED):
|
|
case (SESSION_INACTIVE): /* release request */
|
|
return OEMCrypto_SUCCESS;
|
|
default:
|
|
goto err;
|
|
}
|
|
case API_LOADPROVISIONING:
|
|
switch (session->state) {
|
|
case (SESSION_WAIT_FOR_PROVISIONING):
|
|
return OEMCrypto_SUCCESS;
|
|
default:
|
|
goto err;
|
|
}
|
|
case API_LOADOEMPRIVATEKEY:
|
|
switch (session->state) {
|
|
case (SESSION_OPENED):
|
|
case (SESSION_PREPARING_REQUEST):
|
|
return OEMCrypto_SUCCESS;
|
|
default:
|
|
goto err;
|
|
}
|
|
case API_LOADDRMPRIVATEKEY:
|
|
switch (session->state) {
|
|
case (SESSION_OPENED):
|
|
case (SESSION_PREPARING_REQUEST):
|
|
case (SESSION_USAGE_ENTRY_LOADED):
|
|
// This can happen when using testing the CDM with a pre-provisioned
|
|
// device.
|
|
case (SESSION_LOAD_DRM_RSA_KEY):
|
|
return OEMCrypto_SUCCESS;
|
|
default:
|
|
goto err;
|
|
}
|
|
case API_INSTALLOEMPRIVATEKEY:
|
|
switch (session->state) {
|
|
case (SESSION_OPENED):
|
|
case (SESSION_PREPARING_REQUEST):
|
|
case (SESSION_USAGE_ENTRY_LOADED):
|
|
return OEMCrypto_SUCCESS;
|
|
default:
|
|
goto err;
|
|
}
|
|
case API_GENERATECERTIFICATEKEYPAIR:
|
|
switch (session->state) {
|
|
case (SESSION_OPENED):
|
|
case (SESSION_PREPARING_REQUEST):
|
|
case (SESSION_USAGE_ENTRY_LOADED):
|
|
case (SESSION_INSTALL_OEM_PRIVATE_KEY):
|
|
return OEMCrypto_SUCCESS;
|
|
default:
|
|
goto err;
|
|
}
|
|
case API_GENERATERSASIGNATURE:
|
|
switch (session->state) {
|
|
case (SESSION_OPENED):
|
|
case (SESSION_LOAD_OEM_RSA_KEY):
|
|
case (SESSION_LOAD_DRM_RSA_KEY):
|
|
return OEMCrypto_SUCCESS;
|
|
default:
|
|
goto err;
|
|
}
|
|
case API_CREATENEWUSAGEENTRY:
|
|
switch (session->state) {
|
|
case (SESSION_OPENED):
|
|
case (SESSION_LOAD_DRM_RSA_KEY):
|
|
case (SESSION_WAIT_FOR_LICENSE):
|
|
return OEMCrypto_SUCCESS;
|
|
default:
|
|
goto err;
|
|
}
|
|
case API_LOADUSAGEENTRY:
|
|
switch (session->state) {
|
|
case (SESSION_OPENED):
|
|
case (SESSION_LOAD_DRM_RSA_KEY):
|
|
return OEMCrypto_SUCCESS;
|
|
default:
|
|
goto err;
|
|
}
|
|
case API_REUSEUSAGEENTRY:
|
|
switch (session->state) {
|
|
case (SESSION_OPENED):
|
|
case (SESSION_LOAD_DRM_RSA_KEY):
|
|
return OEMCrypto_SUCCESS;
|
|
default:
|
|
goto err;
|
|
}
|
|
case API_CREATEENTITLEDKEYSESSION:
|
|
switch (session->state) {
|
|
case (SESSION_OPENED):
|
|
case (SESSION_USAGE_ENTRY_LOADED):
|
|
case (SESSION_WAIT_FOR_LICENSE):
|
|
case (SESSION_LICENSE_LOADED):
|
|
case (SESSION_EXPIRED):
|
|
case (SESSION_PLAYING):
|
|
return OEMCrypto_SUCCESS;
|
|
default:
|
|
goto err;
|
|
}
|
|
case API_SELECTKEY:
|
|
case API_LOADENTITLEDCONTENTKEYS:
|
|
case API_QUERYKEYCONTROL:
|
|
switch (session->state) {
|
|
case (SESSION_USAGE_ENTRY_LOADED):
|
|
case (SESSION_LICENSE_LOADED):
|
|
case (SESSION_EXPIRED):
|
|
case (SESSION_PLAYING):
|
|
return OEMCrypto_SUCCESS;
|
|
default:
|
|
goto err;
|
|
}
|
|
case API_DEACTIVATEUSAGEENTRY:
|
|
switch (session->state) {
|
|
case (SESSION_USAGE_ENTRY_LOADED):
|
|
case (SESSION_LICENSE_LOADED):
|
|
case (SESSION_EXPIRED):
|
|
case (SESSION_PLAYING):
|
|
case (SESSION_INACTIVE):
|
|
return OEMCrypto_SUCCESS;
|
|
default:
|
|
goto err;
|
|
}
|
|
case API_REFRESHKEYS:
|
|
case API_LOADRENEWAL:
|
|
switch (session->state) {
|
|
case (SESSION_LICENSE_LOADED):
|
|
case (SESSION_EXPIRED):
|
|
case (SESSION_PLAYING):
|
|
return OEMCrypto_SUCCESS;
|
|
default:
|
|
goto err;
|
|
}
|
|
case API_DECRYPTCENC:
|
|
case API_GENERICENCRYPT:
|
|
case API_GENERICDECRYPT:
|
|
case API_GENERICSIGN:
|
|
case API_GENERICVERIFY:
|
|
switch (session->state) {
|
|
case (SESSION_OPENED):
|
|
case (SESSION_LICENSE_LOADED):
|
|
case (SESSION_LOAD_DRM_RSA_KEY):
|
|
case (SESSION_PLAYING):
|
|
return OEMCrypto_SUCCESS;
|
|
default:
|
|
goto err;
|
|
}
|
|
case API_MOVEENTRY:
|
|
switch (session->state) {
|
|
case (SESSION_USAGE_ENTRY_LOADED):
|
|
case (SESSION_OPENED):
|
|
case (SESSION_WAIT_FOR_LICENSE):
|
|
case (SESSION_LOAD_DRM_RSA_KEY):
|
|
return OEMCrypto_SUCCESS;
|
|
default:
|
|
goto err;
|
|
}
|
|
case API_LOADKEYS:
|
|
case API_LOADLICENSE:
|
|
case API_REPORTUSAGE:
|
|
case API_UPDATEUSAGEENTRY:
|
|
case API_GETHASHERRORCODE:
|
|
case API_SETDECRYPTHASH:
|
|
switch (session->state) {
|
|
case (SESSION_INVALID):
|
|
goto err;
|
|
default:
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
case API_CLOSESESSION:
|
|
return OEMCrypto_SUCCESS;
|
|
default:
|
|
LOGE("OPKI_CheckStatePreCall failed: API=%#x, current state=%#x",
|
|
(unsigned int)api, (unsigned int)(session->state));
|
|
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
err:;
|
|
LOGE("OPKI_CheckStatePreCall failed: API=%#x, current state=%#x",
|
|
(unsigned int)api, (unsigned int)(session->state));
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
|
|
OEMCryptoResult OPKI_SetStatePostCall(OEMCryptoSession* session,
|
|
OEMCryptoSessionAPI api) {
|
|
if (session == NULL) return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
switch (api) {
|
|
case API_OPENSESSION:
|
|
if (session->drm_private_key) {
|
|
session->state = SESSION_LOAD_DRM_RSA_KEY;
|
|
} else {
|
|
session->state = SESSION_OPENED;
|
|
}
|
|
break;
|
|
case API_GENERATENONCE:
|
|
session->state = SESSION_PREPARING_REQUEST;
|
|
break;
|
|
case API_PREPANDSIGN_LICENSE_REQUEST:
|
|
session->state = SESSION_WAIT_FOR_LICENSE;
|
|
break;
|
|
case API_PREPANDSIGN_PROVISION_REQUEST:
|
|
session->state = SESSION_WAIT_FOR_PROVISIONING;
|
|
break;
|
|
case API_LOADKEYS:
|
|
case API_LOADLICENSE:
|
|
case API_LOADRENEWAL:
|
|
case API_REFRESHKEYS:
|
|
session->state = SESSION_LICENSE_LOADED;
|
|
break;
|
|
case API_LOADPROVISIONING:
|
|
session->state = SESSION_PROVISIONED;
|
|
break;
|
|
case API_GENERATERSASIGNATURE:
|
|
session->state = SESSION_CAST_RECEIVER;
|
|
break;
|
|
case API_LOADUSAGEENTRY:
|
|
session->state = SESSION_USAGE_ENTRY_LOADED;
|
|
break;
|
|
case API_DEACTIVATEUSAGEENTRY:
|
|
session->state = SESSION_INACTIVE;
|
|
break;
|
|
case API_LOADDRMPRIVATEKEY:
|
|
session->state = SESSION_LOAD_DRM_RSA_KEY;
|
|
break;
|
|
case API_LOADOEMPRIVATEKEY:
|
|
session->state = SESSION_LOAD_OEM_RSA_KEY;
|
|
break;
|
|
case API_INSTALLOEMPRIVATEKEY:
|
|
session->state = SESSION_INSTALL_OEM_PRIVATE_KEY;
|
|
break;
|
|
case API_DECRYPTCENC:
|
|
case API_GENERICENCRYPT:
|
|
case API_GENERICDECRYPT:
|
|
case API_GENERICSIGN:
|
|
case API_GENERICVERIFY:
|
|
session->state = SESSION_PLAYING;
|
|
break;
|
|
case API_GENERATEDERIVEDKEYS:
|
|
case API_DERIVEKEYSFROMSESSIONKEY:
|
|
case API_PREPANDSIGN_RENEWAL_REQUEST:
|
|
case API_CREATENEWUSAGEENTRY:
|
|
case API_REUSEUSAGEENTRY:
|
|
case API_LOADENTITLEDCONTENTKEYS:
|
|
case API_SELECTKEY:
|
|
case API_QUERYKEYCONTROL:
|
|
case API_MOVEENTRY:
|
|
case API_REPORTUSAGE:
|
|
case API_UPDATEUSAGEENTRY:
|
|
case API_SETDECRYPTHASH:
|
|
case API_GETHASHERRORCODE:
|
|
case API_CREATEENTITLEDKEYSESSION:
|
|
case API_GENERATECERTIFICATEKEYPAIR:
|
|
/* State does not change. */
|
|
break;
|
|
default:
|
|
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult OPKI_SetNonce(OEMCryptoSession* session, uint32_t nonce) {
|
|
if (session == NULL) return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
if (session->nonce_created) return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
if (nonce == 0) return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
const OEMCryptoResult result =
|
|
ODK_SetNonceValues(&session->nonce_values, nonce);
|
|
if (result == OEMCrypto_SUCCESS) session->nonce_created = true;
|
|
return result;
|
|
}
|
|
|
|
OEMCryptoResult OPKI_LoadDRMKey(OEMCryptoSession* session,
|
|
AsymmetricKeyType key_type,
|
|
const uint8_t* wrapped_key,
|
|
size_t wrapped_key_length, size_t key_size,
|
|
uint32_t allowed_schemes) {
|
|
if (session == NULL || wrapped_key == NULL || wrapped_key_length == 0 ||
|
|
!IsSupportedDrmKeyType(key_type)) {
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
// Free any existing keys. This will only happen if we are using the test RSA
|
|
// key and then try to load an existing device certificate.
|
|
OEMCryptoResult result =
|
|
OPKI_FreeAsymmetricKeyFromTable(&session->drm_private_key);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
|
|
if (key_type != DRM_RSA_PRIVATE_KEY) {
|
|
allowed_schemes = 0;
|
|
}
|
|
|
|
result =
|
|
OPKI_CreateAsymmetricKey(&session->drm_private_key, key_type, wrapped_key,
|
|
wrapped_key_length, key_size, allowed_schemes);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to create asymmetric DRM private key with result: %u", result);
|
|
return result;
|
|
}
|
|
if (key_type == DRM_RSA_PRIVATE_KEY) {
|
|
session->allowed_schemes = allowed_schemes;
|
|
} else {
|
|
session->allowed_schemes = 0;
|
|
}
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult OPKI_LoadProv40OEMKey(OEMCryptoSession* session,
|
|
AsymmetricKeyType key_type,
|
|
const uint8_t* wrapped_key,
|
|
size_t wrapped_key_length,
|
|
size_t key_size,
|
|
uint32_t allowed_schemes) {
|
|
if (session == NULL || wrapped_key == NULL || wrapped_key_length == 0 ||
|
|
!IsSupportedDrmKeyType(key_type)) {
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
// Free any existing keys. This will only happen if we are using the test RSA
|
|
// key and then try to load an existing device certificate.
|
|
OEMCryptoResult result =
|
|
OPKI_FreeAsymmetricKeyFromTable(&session->prov40_oem_private_key);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
|
|
if (key_type != DRM_RSA_PRIVATE_KEY) {
|
|
allowed_schemes = 0;
|
|
}
|
|
|
|
result = OPKI_CreateAsymmetricKey(&session->prov40_oem_private_key, key_type,
|
|
wrapped_key, wrapped_key_length, key_size,
|
|
allowed_schemes);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to create asymmetric OEM private key with result: %u", result);
|
|
return result;
|
|
}
|
|
session->prov40_oem_allowed_schemes = allowed_schemes;
|
|
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
NO_IGNORE_RESULT static OEMCryptoResult DeriveKey(
|
|
WTPI_K1_SymmetricKey_Handle master_key, uint8_t counter,
|
|
const uint8_t* context, size_t context_length,
|
|
SymmetricKeyType out_key_type, KeySize out_key_size,
|
|
SymmetricKey** new_key) {
|
|
if (new_key == NULL) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
// TODO(fredgc): remove this from the key table, too.
|
|
OEMCryptoResult result = OPKI_CreateKey(new_key, out_key_type, out_key_size);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
if (*new_key == NULL) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
WTPI_K1_SymmetricKey_Handle* handle = &(*new_key)->key_handle;
|
|
result = WTPI_K1_DeriveKeyFromKeyHandle(master_key, counter, context,
|
|
context_length, out_key_type,
|
|
out_key_size, handle);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to derive key from handle with result: %u", result);
|
|
OPKI_FreeKeyFromTable(new_key);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
NO_IGNORE_RESULT OEMCryptoResult OPKI_DeriveMacAndEncryptionKeys(
|
|
OEMCryptoSession* session, WTPI_K1_SymmetricKey_Handle master_key,
|
|
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 (session == NULL || mac_key_context == NULL ||
|
|
mac_key_context_length == 0 || enc_key_context == NULL ||
|
|
enc_key_context_length == 0 || master_key == NULL) {
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
|
|
uint8_t counter = 1;
|
|
OEMCryptoResult result =
|
|
DeriveKey(master_key, counter, mac_key_context, mac_key_context_length,
|
|
MAC_KEY_SERVER, MAC_KEY_SIZE, &session->mac_key_server);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
counter += 2; // increment counter by 2 because 2 blocks used for server key.
|
|
result =
|
|
DeriveKey(master_key, counter, mac_key_context, mac_key_context_length,
|
|
MAC_KEY_CLIENT, MAC_KEY_SIZE, &session->mac_key_client);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
OPKI_FreeKeyFromTable(&session->mac_key_server);
|
|
return result;
|
|
}
|
|
counter = 1; // Start over with encryption context.
|
|
result =
|
|
DeriveKey(master_key, counter, enc_key_context, enc_key_context_length,
|
|
ENCRYPTION_KEY, KEY_SIZE_128, &session->encryption_key);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
OPKI_FreeKeyFromTable(&session->mac_key_server);
|
|
OPKI_FreeKeyFromTable(&session->mac_key_client);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
OEMCryptoResult OPKI_VerifySignatureWithMacKeyServer(OEMCryptoSession* session,
|
|
const uint8_t* message,
|
|
size_t message_length,
|
|
const uint8_t* signature) {
|
|
if (session == NULL || message == NULL || message_length == 0 ||
|
|
signature == NULL) {
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
if (!OPKI_CheckKey(session->mac_key_server, MAC_KEY_SERVER)) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
return WTPI_C1_HMAC_SHA256_Verify(session->mac_key_server->key_handle,
|
|
message, message_length, signature);
|
|
}
|
|
|
|
OEMCryptoResult OPKI_GenerateSignatureWithMacKeyClient(
|
|
OEMCryptoSession* session, const uint8_t* message, size_t message_length,
|
|
uint8_t* signature, size_t* signature_length) {
|
|
if (session == NULL || message == NULL || message_length == 0 ||
|
|
signature == NULL || signature_length == NULL) {
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
if (!OPKI_CheckKey(session->mac_key_client, MAC_KEY_CLIENT)) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (*signature_length != SHA256_DIGEST_LENGTH) {
|
|
*signature_length = SHA256_DIGEST_LENGTH;
|
|
return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
}
|
|
OEMCryptoResult result = WTPI_C1_HMAC_SHA256(
|
|
session->mac_key_client->key_handle, message, message_length, signature);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to compute HMAC-SHA256 signature with result: %u", result);
|
|
return result;
|
|
}
|
|
*signature_length = SHA256_DIGEST_LENGTH;
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
NO_IGNORE_RESULT static bool DRMKeyMaySign(const OEMCryptoSession* session) {
|
|
switch (session->drm_private_key->key_type) {
|
|
case DRM_RSA_PRIVATE_KEY:
|
|
return (session->allowed_schemes & kSign_RSASSA_PSS) == kSign_RSASSA_PSS;
|
|
case DRM_ECC_PRIVATE_KEY:
|
|
return true;
|
|
case PROV40_ED25519_PRIVATE_KEY:
|
|
// DRM key can never be ED25519 key.
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
OEMCryptoResult OPKI_GenerateCertSignature(OEMCryptoSession* session,
|
|
const uint8_t* message,
|
|
size_t message_length,
|
|
uint8_t* signature,
|
|
size_t* signature_length,
|
|
CertSignatureType signature_type) {
|
|
if (session == NULL || message == NULL || message_length == 0 ||
|
|
signature == NULL || signature_length == NULL) {
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
switch (signature_type) {
|
|
case CERT_SIGNATURE_OEM:
|
|
// TODO(b/225216277): implement this.
|
|
// return SignMessageWithOEMPrivateKey(message, message_length, signature,
|
|
// signature_length);
|
|
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
|
case CERT_SIGNATURE_DRM: {
|
|
if (!DRMKeyMaySign(session)) return OEMCrypto_ERROR_INVALID_KEY;
|
|
WTPI_AsymmetricKey_Handle drm_private_key_handle;
|
|
uint32_t allowed_schemes;
|
|
AsymmetricKey* drm_private_key = session->drm_private_key;
|
|
OEMCryptoResult result = WTPI_UnwrapIntoAsymmetricKeyHandle(
|
|
drm_private_key->wrapped_key, drm_private_key->wrapped_key_length,
|
|
drm_private_key->key_type, &drm_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;
|
|
}
|
|
if (drm_private_key->key_type == DRM_RSA_PRIVATE_KEY) {
|
|
result = WTPI_RSASign(drm_private_key_handle, message, message_length,
|
|
signature, signature_length, kSign_RSASSA_PSS);
|
|
} else {
|
|
result = WTPI_ECCSign(drm_private_key_handle, message, message_length,
|
|
signature, signature_length);
|
|
}
|
|
WTPI_FreeAsymmetricKeyHandle(drm_private_key_handle);
|
|
return result;
|
|
}
|
|
default:
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
}
|
|
|
|
NO_IGNORE_RESULT static OEMCryptoResult CheckStatusOnline(
|
|
OEMCryptoSession* session, uint32_t nonce, uint32_t control UNUSED) {
|
|
if (!session) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
if (session->nonce_values.nonce != nonce) {
|
|
return OEMCrypto_ERROR_INVALID_NONCE;
|
|
}
|
|
switch (OPKI_GetUsageEntryStatus(session->session_id)) {
|
|
default: /* Invalid status. */
|
|
case USAGE_ENTRY_NONE:
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
case USAGE_ENTRY_LOADED:
|
|
/* Cannot reload usage entry for online license. */
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
case USAGE_ENTRY_NEW:
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
}
|
|
|
|
NO_IGNORE_RESULT static OEMCryptoResult CheckStatusOffline(
|
|
OEMCryptoSession* session, uint32_t nonce, uint32_t control UNUSED) {
|
|
if (!session) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
switch (OPKI_GetUsageEntryStatus(session->session_id)) {
|
|
default: /* Invalid status. */
|
|
case USAGE_ENTRY_NONE:
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
case USAGE_ENTRY_LOADED:
|
|
/* We do not check the nonce. Instead, LoadKeys will verify the pst and
|
|
* mac keys match. */
|
|
return OEMCrypto_SUCCESS;
|
|
case USAGE_ENTRY_NEW:
|
|
if (session->nonce_values.nonce != nonce) {
|
|
return OEMCrypto_ERROR_INVALID_NONCE;
|
|
}
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
}
|
|
|
|
NO_IGNORE_RESULT static OEMCryptoResult check_nonce_or_entry(
|
|
OEMCryptoSession* session, KeyControlBlock key_control_block) {
|
|
/* Note: we only check the nonce if the bit is enabled. */
|
|
uint32_t replay_bits = key_control_block.control_bits & CONTROL_REPLAY_MASK;
|
|
switch (replay_bits) {
|
|
case CONTROL_NONCE_REQUIRED:
|
|
/* Online license. Nonce always required. */
|
|
return CheckStatusOnline(session, key_control_block.nonce,
|
|
key_control_block.control_bits);
|
|
break;
|
|
case CONTROL_NONCE_OR_ENTRY:
|
|
/* Offline license. Nonce required on first load. */
|
|
return CheckStatusOffline(session, key_control_block.nonce,
|
|
key_control_block.control_bits);
|
|
break;
|
|
default:
|
|
if ((key_control_block.control_bits & CONTROL_NONCE_ENABLED) &&
|
|
session->nonce_values.nonce != key_control_block.nonce) {
|
|
return OEMCrypto_ERROR_INVALID_NONCE;
|
|
}
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
}
|
|
|
|
OEMCryptoResult OPKI_InstallKey(OEMCryptoSession* session,
|
|
const uint8_t* key_id, size_t key_id_length,
|
|
const uint8_t* key_data, size_t key_data_length,
|
|
const uint8_t* key_data_iv,
|
|
const uint8_t* key_control,
|
|
const uint8_t* key_control_iv) {
|
|
if (session == NULL || key_id == NULL || key_id_length == 0 ||
|
|
key_id_length > KEY_ID_MAX_SIZE || key_data == NULL ||
|
|
key_data_length == 0 || key_data_iv == NULL || key_control == NULL) {
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
uint8_t raw_key_control[KEY_CONTROL_SIZE];
|
|
|
|
SymmetricKey** current_key_ptr;
|
|
uint32_t current_key_index = 0;
|
|
SymmetricKeyType key_type;
|
|
uint8_t minimum_patch_level = 0;
|
|
if (session->license_type == OEMCrypto_ContentLicense) {
|
|
if (key_data_length != KEY_SIZE_128 && key_data_length != KEY_SIZE_256) {
|
|
/* Generic crypto allows both key sizes. */
|
|
LOGE("Invalid key data length: %zu", key_data_length);
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
current_key_index = session->num_content_keys;
|
|
if (current_key_index == CONTENT_KEYS_PER_SESSION) {
|
|
return OEMCrypto_ERROR_INSUFFICIENT_RESOURCES;
|
|
}
|
|
current_key_ptr = &session->content_keys[current_key_index];
|
|
key_type = CONTENT_KEY;
|
|
} else {
|
|
if (key_data_length != KEY_SIZE_256) return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
current_key_index = session->num_entitlement_keys;
|
|
if (current_key_index == ENTITLEMENT_KEYS_PER_SESSION) {
|
|
return OEMCrypto_ERROR_INSUFFICIENT_RESOURCES;
|
|
}
|
|
current_key_ptr = &session->entitlement_keys[current_key_index];
|
|
key_type = ENTITLEMENT_KEY;
|
|
}
|
|
|
|
if (!OPKI_CheckKey(session->encryption_key, ENCRYPTION_KEY)) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
OEMCryptoResult result =
|
|
OPKI_CreateKey(current_key_ptr, key_type, (KeySize)key_data_length);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
SymmetricKey* current_key = *current_key_ptr;
|
|
result = WTPI_K1_AESDecryptAndCreateKeyHandle(
|
|
session->encryption_key->key_handle, key_data, key_data_length,
|
|
key_data_iv, key_type, &(current_key->key_handle));
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE(
|
|
"Failed to AES CBC decrypt key data and create key handle with result: "
|
|
"%u",
|
|
result);
|
|
goto cleanup;
|
|
}
|
|
|
|
memcpy(current_key->key_id, key_id, key_id_length);
|
|
current_key->key_id_size = key_id_length;
|
|
current_key->session_key_index = current_key_index;
|
|
|
|
if (!OPKI_CheckKey(current_key, key_type)) {
|
|
result = OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
goto cleanup;
|
|
}
|
|
/* To address backwards compatibility issues with a v16.x server SDK bug, the
|
|
* exact rules for determining whether a KCB is encrypted or clear have
|
|
* changed.
|
|
*
|
|
* Original behavior:
|
|
* - Version <= 16.4.x --> KCB encrypted
|
|
* - Version >= 16.5.x --> KCB clear
|
|
* New behavior:
|
|
* - No KCB IV --> KCB clear
|
|
* - KCB IV --> KCB encrypted
|
|
*/
|
|
if (key_control_iv != NULL) {
|
|
if (IsExpectedClearKeyControlBlockVersion(&session->nonce_values)) {
|
|
LOGW("Unexpected encrypted KCB: response_odk_version = %u.%u",
|
|
session->nonce_values.api_major_version,
|
|
session->nonce_values.api_minor_version);
|
|
}
|
|
/* We use the first 128 bits regardless of the license type to decrypt the
|
|
key control. */
|
|
result = WTPI_C1_AESCBCDecrypt(current_key->key_handle, KEY_SIZE_128,
|
|
key_control, KEY_CONTROL_SIZE,
|
|
key_control_iv, raw_key_control);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to AES CBC decrypt key control data with result: %u",
|
|
result);
|
|
goto cleanup;
|
|
}
|
|
} else {
|
|
if (!IsExpectedClearKeyControlBlockVersion(&session->nonce_values)) {
|
|
LOGW("Unexpected clear KCB: response_odk_version = %u.%u",
|
|
session->nonce_values.api_major_version,
|
|
session->nonce_values.api_minor_version);
|
|
}
|
|
memcpy(raw_key_control, key_control, KEY_CONTROL_SIZE);
|
|
}
|
|
|
|
result = OPKI_ParseKeyControlBlock(raw_key_control,
|
|
¤t_key->key_control_block);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to parse key control block with result: %u", result);
|
|
goto cleanup;
|
|
}
|
|
if (!(current_key->key_control_block.valid)) {
|
|
LOGE("Key control block is not valid");
|
|
result = OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
goto cleanup;
|
|
}
|
|
if ((current_key->key_control_block.control_bits &
|
|
CONTROL_REQUIRE_ANTI_ROLLBACK_HARDWARE) &&
|
|
!WTPI_IsAntiRollbackHWPresent()) {
|
|
LOGE("Key control requires anti-rollback hardware, which is not present");
|
|
result = OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
goto cleanup;
|
|
}
|
|
minimum_patch_level = (current_key->key_control_block.control_bits &
|
|
CONTROL_SECURITY_PATCH_LEVEL_MASK) >>
|
|
CONTROL_SECURITY_PATCH_LEVEL_SHIFT;
|
|
if (minimum_patch_level > OEMCrypto_Security_Patch_Level()) {
|
|
LOGE(
|
|
"Security patch level doesn't meet the minimum requirement. Current: "
|
|
"%u, required: %u",
|
|
OEMCrypto_Security_Patch_Level(), minimum_patch_level);
|
|
result = OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
goto cleanup;
|
|
}
|
|
result = check_nonce_or_entry(session, current_key->key_control_block);
|
|
if (result != OEMCrypto_SUCCESS) goto cleanup;
|
|
if (current_key->key_control_block.control_bits &
|
|
CONTROL_SRM_VERSION_REQUIRED) {
|
|
if (!session->valid_srm_version) {
|
|
/* Require local display only. */
|
|
current_key->key_control_block.control_bits |= CONTROL_HDCP_VERSION_MASK;
|
|
}
|
|
}
|
|
if (session->license_type == OEMCrypto_ContentLicense) {
|
|
current_key->is_entitled_content_key = false;
|
|
}
|
|
|
|
cleanup:
|
|
if (result != OEMCrypto_SUCCESS) OPKI_FreeKeyFromTable(current_key_ptr);
|
|
return result;
|
|
}
|
|
|
|
OEMCryptoResult OPKI_UpdateMacKeys(OEMCryptoSession* session,
|
|
const uint8_t* enc_mac_keys,
|
|
const uint8_t* mac_keys_iv) {
|
|
if (session == NULL || enc_mac_keys == NULL || mac_keys_iv == NULL) {
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
if (!OPKI_CheckKey(session->encryption_key, ENCRYPTION_KEY)) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
OEMCryptoResult free_mac_key_server_result =
|
|
OPKI_FreeKeyFromTable(&session->mac_key_server);
|
|
OEMCryptoResult free_mac_key_client_result =
|
|
OPKI_FreeKeyFromTable(&session->mac_key_client);
|
|
if (free_mac_key_server_result != OEMCrypto_SUCCESS) {
|
|
return free_mac_key_server_result;
|
|
}
|
|
if (free_mac_key_client_result != OEMCrypto_SUCCESS) {
|
|
return free_mac_key_client_result;
|
|
}
|
|
OEMCryptoResult result =
|
|
OPKI_CreateKey(&session->mac_key_server, MAC_KEY_SERVER, MAC_KEY_SIZE);
|
|
if (result != OEMCrypto_SUCCESS) goto cleanup;
|
|
result =
|
|
OPKI_CreateKey(&session->mac_key_client, MAC_KEY_CLIENT, MAC_KEY_SIZE);
|
|
if (result != OEMCrypto_SUCCESS) goto cleanup;
|
|
result = WTPI_K1_AESDecryptAndCreateKeyHandleForMacKeys(
|
|
session->encryption_key->key_handle, enc_mac_keys, 2 * MAC_KEY_SIZE,
|
|
mac_keys_iv, &(session->mac_key_server->key_handle),
|
|
&(session->mac_key_client->key_handle));
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE(
|
|
"Failed to AES CBC decrypt mac keys and create handles with result: %u",
|
|
result);
|
|
return result;
|
|
}
|
|
cleanup:
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
OPKI_FreeKeyFromTable(&session->mac_key_server);
|
|
OPKI_FreeKeyFromTable(&session->mac_key_client);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
OEMCryptoResult OPKI_RefreshKey(OEMCryptoSession* session,
|
|
const uint8_t* key_id, size_t key_id_length,
|
|
const uint8_t* key_control,
|
|
const uint8_t* key_control_iv) {
|
|
if (session == NULL || key_control == NULL) {
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
|
|
OEMCryptoResult result = OEMCrypto_SUCCESS;
|
|
KeyControlBlock key_control_block;
|
|
if (key_id == NULL) {
|
|
result = OPKI_ParseKeyControlBlock(key_control, &key_control_block);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to parse key control block with result: %u", result);
|
|
return result;
|
|
}
|
|
} else {
|
|
bool is_content_key = session->license_type == OEMCrypto_ContentLicense;
|
|
SymmetricKey* key =
|
|
OPKI_FindKeyFromTable(session, is_content_key, key_id, key_id_length);
|
|
if (key == NULL) return OEMCrypto_ERROR_NO_CONTENT_KEY;
|
|
|
|
uint8_t raw_key_control[KEY_CONTROL_SIZE];
|
|
if (key_control_iv != NULL) {
|
|
SymmetricKeyType key_type;
|
|
if (is_content_key) {
|
|
key_type = CONTENT_KEY;
|
|
} else {
|
|
key_type = ENTITLEMENT_KEY;
|
|
}
|
|
if (!OPKI_CheckKey(key, key_type)) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
/* Decrypt using only the first 128 bits of the key. */
|
|
result = WTPI_C1_AESCBCDecrypt(key->key_handle, KEY_SIZE_128, key_control,
|
|
KEY_CONTROL_SIZE, key_control_iv,
|
|
raw_key_control);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to AES CBC decrypt key control with result: %u", result);
|
|
return result;
|
|
}
|
|
} else {
|
|
memcpy(raw_key_control, key_control, KEY_CONTROL_SIZE);
|
|
}
|
|
result = OPKI_ParseKeyControlBlock(raw_key_control, &key_control_block);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to parse raw key control with result: %u", result);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
if (!key_control_block.valid) return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
if ((key_control_block.control_bits & CONTROL_NONCE_ENABLED) &&
|
|
session->nonce_values.nonce != key_control_block.nonce) {
|
|
return OEMCrypto_ERROR_INVALID_NONCE;
|
|
}
|
|
|
|
uint32_t new_key_duration = key_control_block.duration;
|
|
uint64_t* timer_value = NULL;
|
|
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_RefreshV15Values(&session->timer_limits, &session->clock_values,
|
|
&session->nonce_values, now, new_key_duration,
|
|
timer_value);
|
|
if (result == ODK_SET_TIMER || result == ODK_DISABLE_TIMER) {
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
if (result == ODK_TIMER_EXPIRED) return OEMCrypto_ERROR_KEY_EXPIRED;
|
|
return result;
|
|
}
|
|
|
|
OEMCryptoResult OPKI_GetCurrentContentKey(
|
|
OEMCryptoSession* session, OEMCryptoEntitledKeySession* key_session,
|
|
SymmetricKey** key) {
|
|
RETURN_INVALID_CONTEXT_IF_NULL(session);
|
|
RETURN_INVALID_CONTEXT_IF_NULL(key);
|
|
if (session->license_type == OEMCrypto_EntitlementLicense) {
|
|
RETURN_INVALID_CONTEXT_IF_NULL(key_session);
|
|
if (key_session->current_entitled_content_key_index >=
|
|
key_session->num_entitled_content_keys) {
|
|
LOGE("Entitled content key index out of range, index = %u",
|
|
key_session->current_entitled_content_key_index);
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
*key = key_session->entitled_content_keys
|
|
[key_session->current_entitled_content_key_index];
|
|
} else {
|
|
if (session->current_content_key_index >= session->num_content_keys) {
|
|
LOGE("Content key index out of range, index = %u",
|
|
session->current_content_key_index);
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
*key = session->content_keys[session->current_content_key_index];
|
|
}
|
|
if (*key == NULL) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult OPKI_GetEntitlementKey(OEMCryptoSession* session,
|
|
const uint8_t* key_id,
|
|
size_t key_id_size, SymmetricKey** key) {
|
|
RETURN_INVALID_CONTEXT_IF_NULL(session);
|
|
RETURN_INVALID_CONTEXT_IF_NULL(key_id);
|
|
RETURN_INVALID_CONTEXT_IF_ZERO(key_id_size);
|
|
RETURN_INVALID_CONTEXT_IF_NULL(key);
|
|
if (session->license_type != OEMCrypto_EntitlementLicense) {
|
|
LOGE("Unexpected license type %u", session->license_type);
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
for (uint32_t i = 0; i < session->num_entitlement_keys; i++) {
|
|
SymmetricKey* cur_key = session->entitlement_keys[i];
|
|
if (cur_key != NULL && cur_key->key_id_size == (uint8_t)key_id_size &&
|
|
memcmp(cur_key->key_id, key_id, key_id_size) == 0) {
|
|
*key = cur_key;
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
}
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
|
|
OEMCryptoResult OPKI_GetKeyControlBlock(
|
|
SymmetricKey* content_key, OEMCryptoSession* session,
|
|
OEMCryptoEntitledKeySession* key_session, KeyControlBlock* control_block) {
|
|
RETURN_INVALID_CONTEXT_IF_NULL(control_block);
|
|
RETURN_INVALID_CONTEXT_IF_NULL(content_key);
|
|
RETURN_INVALID_CONTEXT_IF_NULL(session);
|
|
if (content_key->is_entitled_content_key) {
|
|
RETURN_INVALID_CONTEXT_IF_NULL(key_session);
|
|
if (session->license_type != OEMCrypto_EntitlementLicense) {
|
|
LOGE("License type %u mismatch for entitled content key",
|
|
session->license_type);
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
/* For entitled content key, get the corresponding entitlement key which
|
|
* holds the key control block. */
|
|
ABORT_IF(content_key->session_key_index >=
|
|
key_session->num_entitled_content_keys,
|
|
"Invalid content key index.");
|
|
EntitlementKeyInfo* entitlement_key_info =
|
|
&key_session->entitlement_keys[content_key->session_key_index];
|
|
ABORT_IF_ZERO(entitlement_key_info->entitlement_key_id_size);
|
|
SymmetricKey* entitlement_key = NULL;
|
|
OEMCryptoResult result = OPKI_GetEntitlementKey(
|
|
session, entitlement_key_info->entitlement_key_id,
|
|
entitlement_key_info->entitlement_key_id_size, &entitlement_key);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE(
|
|
"Could not find entitlement key for entitled content key at index %u",
|
|
content_key->session_key_index);
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (OPKI_CheckKey(entitlement_key, ENTITLEMENT_KEY)) {
|
|
*control_block = entitlement_key->key_control_block;
|
|
} else {
|
|
LOGE("Invalid entitlement key.");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
} else {
|
|
*control_block = content_key->key_control_block;
|
|
}
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
static bool PrioritizeHdcpLevel(OEMCrypto_HDCP_Capability level,
|
|
uint8_t* priority) {
|
|
// Since OEMCrypto_HDCP_Capability is no longer sorted, we need to convert all
|
|
// values to a sorted priority so we can efficiently compare them.
|
|
//
|
|
// If new enum values are added to OEMCrypto_HDCP_Capability, we'll have to
|
|
// add them to this function. Since OEMCrypto_HDCP_Capability is no longer
|
|
// sorted, we cannot assume we know how to handle new values.
|
|
if (level == HDCP_NONE || level == HDCP_V1 ||
|
|
level == HDCP_NO_DIGITAL_OUTPUT) {
|
|
*priority = (uint8_t)level;
|
|
return true;
|
|
} else if (level >= HDCP_V2 && level <= HDCP_V2_3) {
|
|
*priority = (uint8_t)level + 5;
|
|
return true;
|
|
} else if (level >= HDCP_V1_0 && level <= HDCP_V1_4) {
|
|
*priority = (uint8_t)level - 4;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool IsHdcpLevelSufficient(OEMCrypto_HDCP_Capability required,
|
|
OEMCrypto_HDCP_Capability current) {
|
|
uint8_t required_priority = 0;
|
|
if (!PrioritizeHdcpLevel(required, &required_priority)) {
|
|
LOGE("Unrecognized HDCP requirement %u", required);
|
|
return false;
|
|
}
|
|
uint8_t current_priority = 0;
|
|
if (!PrioritizeHdcpLevel(current, ¤t_priority)) {
|
|
LOGE("Unrecognized current HDCP level %u", current);
|
|
return false;
|
|
}
|
|
|
|
return current_priority >= required_priority;
|
|
}
|
|
|
|
OEMCryptoResult OPKI_CheckKeyControlBlock(const KeyControlBlock* control,
|
|
uint32_t use_type,
|
|
OPK_OutputBuffer_Type buffer_type) {
|
|
RETURN_INVALID_CONTEXT_IF_NULL(control);
|
|
if (use_type && (!(control->control_bits & use_type))) {
|
|
/* Could not use this key for the given use type. */
|
|
LOGE("Could not use this key for the given use type: %u", use_type);
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
if (control->control_bits & CONTROL_DATA_PATH_SECURE) {
|
|
if (!WTPI_IsClosedPlatform() &&
|
|
buffer_type == OPK_CLEAR_INSECURE_OUTPUT_BUFFER) {
|
|
return OEMCrypto_ERROR_DECRYPT_FAILED;
|
|
}
|
|
}
|
|
const OEMCrypto_HDCP_Capability capability = WTPI_CurrentHDCPCapability();
|
|
if (capability != HDCP_NO_DIGITAL_OUTPUT &&
|
|
(control->control_bits & CONTROL_HDCP_REQUIRED)) {
|
|
/* Check to see if HDCP requirements are satisfied. */
|
|
const uint8_t required_hdcp =
|
|
(control->control_bits & CONTROL_HDCP_VERSION_MASK) >>
|
|
CONTROL_HDCP_VERSION_SHIFT;
|
|
if (!IsHdcpLevelSufficient(required_hdcp, capability) ||
|
|
capability == HDCP_NONE) {
|
|
return OEMCrypto_ERROR_INSUFFICIENT_HDCP;
|
|
}
|
|
}
|
|
/* Disable the analog outputs, if required. */
|
|
if (control->control_bits & CONTROL_DISABLE_ANALOG_OUTPUT) {
|
|
if (WTPI_IsAnalogDisplayActive() && !WTPI_DisableAnalogDisplay()) {
|
|
LOGE("Analog output could not be disabled");
|
|
return OEMCrypto_ERROR_ANALOG_OUTPUT;
|
|
}
|
|
}
|
|
/* If CGMS-A is required, either it must be active or analog output must be
|
|
disabled. */
|
|
if (control->control_bits & CONTROL_CGMS_MASK) {
|
|
if (!WTPI_IsCGMS_AActive() && WTPI_IsAnalogDisplayActive() &&
|
|
!WTPI_DisableAnalogDisplay()) {
|
|
LOGE("CGMS must be active if analog output is active");
|
|
return OEMCrypto_ERROR_ANALOG_OUTPUT;
|
|
}
|
|
}
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult OPKI_CheckCurrentContentKeyUsage(
|
|
OEMCryptoSession* session, OEMCryptoEntitledKeySession* key_session,
|
|
uint32_t use_type, OPK_OutputBuffer_Type buffer_type) {
|
|
if (session == NULL) return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
if ((session->license_type == OEMCrypto_ContentLicense &&
|
|
key_session != NULL) ||
|
|
(session->license_type == OEMCrypto_EntitlementLicense &&
|
|
key_session == NULL)) {
|
|
LOGE("Mismatched license type and entitled key session.");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
ABORT_IF(buffer_type != OPK_CLEAR_INSECURE_OUTPUT_BUFFER &&
|
|
buffer_type != OPK_SECURE_OUTPUT_BUFFER,
|
|
"Impossible buffer type.");
|
|
SymmetricKey* current_content_key = NULL;
|
|
OEMCryptoResult result =
|
|
OPKI_GetCurrentContentKey(session, key_session, ¤t_content_key);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
if (!OPKI_CheckKey(current_content_key, CONTENT_KEY)) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
KeyControlBlock control;
|
|
result = OPKI_GetKeyControlBlock(current_content_key, session, key_session,
|
|
&control);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
return OPKI_CheckKeyControlBlock(&control, use_type, buffer_type);
|
|
}
|
|
|
|
OEMCryptoResult OPKI_UpdatePlaybackTimeAndUsageEntryStatus(
|
|
OEMCryptoSession* session) {
|
|
if (session == NULL) return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
OEMCryptoResult result;
|
|
uint64_t now;
|
|
result = WTPI_GetTrustedTime(&now);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
/* Update ODK clock values. */
|
|
if (!session->decrypt_started) {
|
|
uint64_t* timer_expiration = NULL;
|
|
result = ODK_AttemptFirstPlayback(now, &session->timer_limits,
|
|
&session->clock_values, timer_expiration);
|
|
} else {
|
|
/* Continued playback. */
|
|
result = ODK_UpdateLastPlaybackTime(now, &session->timer_limits,
|
|
&session->clock_values);
|
|
}
|
|
if (result == ODK_TIMER_EXPIRED) return OEMCrypto_ERROR_KEY_EXPIRED;
|
|
if (result != OEMCrypto_SUCCESS && result != ODK_DISABLE_TIMER &&
|
|
result != ODK_SET_TIMER) {
|
|
/* Results other than ODK_DISABLE_TIMER and ODK_SET_TIMER are treated as
|
|
* errors. */
|
|
return result;
|
|
}
|
|
|
|
/* Update usage entry. */
|
|
switch (OPKI_GetUsageEntryStatus(session->session_id)) {
|
|
case USAGE_ENTRY_NONE:
|
|
/* No entry, don't do anything. */
|
|
break;
|
|
case USAGE_ENTRY_DEACTIVATED:
|
|
return OEMCrypto_ERROR_KEY_EXPIRED;
|
|
case USAGE_ENTRY_NEW:
|
|
case USAGE_ENTRY_LOADED:
|
|
if (!session->decrypt_started) {
|
|
result = OPKI_ForbidReportUsage(session->session_id);
|
|
} else {
|
|
result = OPKI_SetUsageEntryRecentDecrypt(session->session_id);
|
|
}
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
break;
|
|
default:
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
session->decrypt_started = true;
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
NO_IGNORE_RESULT static OEMCryptoResult ComputeDecryptHash(
|
|
OEMCryptoSession* session, OEMCryptoEntitledKeySession* key_session,
|
|
const OEMCrypto_SampleDescription* sample,
|
|
SymmetricKey* current_content_key, OPK_OutputBuffer* output_buffer,
|
|
size_t starting_output_offset) {
|
|
ABORT_IF_NULL(session);
|
|
ABORT_IF_NULL(sample);
|
|
ABORT_IF_NULL(current_content_key);
|
|
ABORT_IF_NULL(output_buffer);
|
|
ABORT_IF(session->license_type == OEMCrypto_EntitlementLicense &&
|
|
key_session == NULL,
|
|
"entitled key session is NULL");
|
|
OEMCryptoResult result = OEMCrypto_SUCCESS;
|
|
bool can_compute_hash = false;
|
|
if (OPKI_CheckKey(current_content_key, CONTENT_KEY)) {
|
|
KeyControlBlock control_block;
|
|
result = OPKI_GetKeyControlBlock(current_content_key, session, key_session,
|
|
&control_block);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
uint32_t control_bits = control_block.control_bits;
|
|
can_compute_hash = (control_bits & CONTROL_ALLOW_HASH_VERIFICATION) != 0;
|
|
}
|
|
|
|
DecryptHash* decrypt_hash =
|
|
(session->license_type == OEMCrypto_EntitlementLicense)
|
|
? &key_session->decrypt_hash
|
|
: &session->decrypt_hash;
|
|
if (!can_compute_hash) {
|
|
decrypt_hash->hash_error = OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
decrypt_hash->compute_hash = false;
|
|
decrypt_hash->current_hash = 0;
|
|
decrypt_hash->current_frame_number = 0;
|
|
} else {
|
|
size_t offset = 0;
|
|
for (size_t subsample_index = 0;
|
|
subsample_index < sample->subsamples_length; ++subsample_index) {
|
|
const OEMCrypto_SubSampleDescription* subsample =
|
|
&(sample->subsamples[subsample_index]);
|
|
size_t subsample_length;
|
|
if (OPK_AddOverflowUX(subsample->num_bytes_clear,
|
|
subsample->num_bytes_encrypted,
|
|
&subsample_length)) {
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
size_t current_offset;
|
|
if (OPK_AddOverflowUX(starting_output_offset, offset, ¤t_offset)) {
|
|
LOGE("Output buffer overflow");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
result = OPK_CheckOutputBounds(output_buffer, current_offset,
|
|
subsample_length);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Output bounds check failed with result: %u", result);
|
|
return result;
|
|
}
|
|
|
|
if (OEMCrypto_FirstSubsample & subsample->subsample_flags) {
|
|
result = WTPI_Crc32Init(&decrypt_hash->current_hash);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to init CRC32 with result: %u", result);
|
|
return result;
|
|
}
|
|
}
|
|
if (subsample_length > 0) {
|
|
result = WTPI_Crc32Cont_OutputBuffer(
|
|
output_buffer, current_offset, subsample_length,
|
|
decrypt_hash->current_hash, &decrypt_hash->current_hash);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to compute CRC32 with result: %u", result);
|
|
return result;
|
|
}
|
|
}
|
|
if (OEMCrypto_LastSubsample & subsample->subsample_flags) {
|
|
if (decrypt_hash->current_hash != decrypt_hash->given_hash) {
|
|
/* Update bad_frame_number if this is the first bad frame. */
|
|
if (decrypt_hash->hash_error == OEMCrypto_SUCCESS) {
|
|
decrypt_hash->bad_frame_number = decrypt_hash->current_frame_number;
|
|
decrypt_hash->hash_error = OEMCrypto_ERROR_BAD_HASH;
|
|
}
|
|
}
|
|
decrypt_hash->compute_hash = false;
|
|
}
|
|
|
|
/* Advance the offset. */
|
|
if (OPK_AddOverflowUX(offset, subsample_length, &offset)) {
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
NO_IGNORE_RESULT static OEMCryptoResult DecryptOneSample(
|
|
OEMCryptoSession* session, OEMCryptoEntitledKeySession* key_session,
|
|
const OEMCrypto_SampleDescription* sample,
|
|
const OEMCrypto_CENCEncryptPatternDesc* pattern) {
|
|
ABORT_IF_NULL(session);
|
|
ABORT_IF_NULL(sample);
|
|
ABORT_IF_NULL(pattern);
|
|
OEMCryptoResult result = OEMCrypto_SUCCESS;
|
|
|
|
OPK_OutputBuffer output_buffer;
|
|
size_t starting_output_offset;
|
|
result = OPK_ParseDestBufferDesc(&sample->buffers.output_descriptor,
|
|
&output_buffer, &starting_output_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.");
|
|
|
|
/* Use 0 as use_type since there is no key control bit indicating whether
|
|
* this key can be used as for DecryptCENC. */
|
|
result = OPKI_CheckCurrentContentKeyUsage(session, key_session, 0,
|
|
output_buffer.type);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to verify current content key usage with result: %u", result);
|
|
return result;
|
|
}
|
|
result = OPKI_UpdatePlaybackTimeAndUsageEntryStatus(session);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
if (result == OEMCrypto_ERROR_KEY_EXPIRED) {
|
|
session->state = SESSION_EXPIRED;
|
|
}
|
|
return result;
|
|
}
|
|
SymmetricKey* current_content_key = NULL;
|
|
result =
|
|
OPKI_GetCurrentContentKey(session, key_session, ¤t_content_key);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
result = WTPI_DecryptSample(current_content_key->key_handle, sample, pattern,
|
|
&output_buffer, starting_output_offset,
|
|
current_content_key->cipher_mode);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
|
|
/* Compute hash for the decrypted sample. */
|
|
bool compute_hash = (session->license_type == OEMCrypto_EntitlementLicense)
|
|
? key_session->decrypt_hash.compute_hash
|
|
: session->decrypt_hash.compute_hash;
|
|
if (compute_hash) {
|
|
result =
|
|
ComputeDecryptHash(session, key_session, sample, current_content_key,
|
|
&output_buffer, starting_output_offset);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
OEMCryptoResult OPKI_DecryptSamples(
|
|
OEMCryptoSession* session, OEMCryptoEntitledKeySession* key_session,
|
|
const OEMCrypto_SampleDescription* samples, size_t samples_length,
|
|
const OEMCrypto_CENCEncryptPatternDesc* pattern) {
|
|
if (session == NULL || samples == NULL || pattern == NULL ||
|
|
samples_length == 0) {
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
if ((session->license_type == OEMCrypto_ContentLicense &&
|
|
key_session != NULL) ||
|
|
(session->license_type == OEMCrypto_EntitlementLicense &&
|
|
key_session == NULL)) {
|
|
LOGE("Mismatched license type and entitled key session.");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
/* Iterate through all the samples and decrypt each one. */
|
|
for (size_t sample_index = 0; sample_index < samples_length; ++sample_index) {
|
|
const OEMCrypto_SampleDescription* sample = &(samples[sample_index]);
|
|
const OEMCryptoResult result =
|
|
DecryptOneSample(session, key_session, sample, pattern);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Failure %#x at sample index %zu", result, sample_index);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult OPKI_GetNonceValues(const OEMCryptoSession* session,
|
|
ODK_NonceValues* nonce_values) {
|
|
if (session == NULL || nonce_values == NULL) {
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
|
|
if (!session->nonce_created) {
|
|
LOGE("Nonce has not been created");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
|
|
*nonce_values = session->nonce_values;
|
|
|
|
return OEMCrypto_SUCCESS;
|
|
}
|