Files
oemcrypto/oemcrypto/opk/oemcrypto_ta/oemcrypto_session.c
Matt Feddersen 8381c79c3e OPK v17.2.1 release
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.
2023-11-10 15:00:35 -08:00

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,
&current_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, &current_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, &current_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, &current_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, &current_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;
}