Files
android/libwvdrmengine/cdm/core/src/crypto_session.cpp

3007 lines
108 KiB
C++

// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
//
// Crypto - wrapper classes for OEMCrypto interface
//
#include "crypto_session.h"
#include <string.h>
#include <algorithm>
#include <iostream>
#include <memory>
#include "advance_iv_ctr.h"
#include "arraysize.h"
#include "content_key_session.h"
#include "crypto_key.h"
#include "entitlement_key_session.h"
#include "log.h"
#include "platform.h"
#include "privacy_crypto.h"
#include "properties.h"
#include "pst_report.h"
#include "string_conversions.h"
#include "usage_table_header.h"
#include "wv_cdm_constants.h"
// Stringify turns macro arguments into static C strings.
// Example: STRINGIFY(this_argument) -> "this_argument"
#define STRINGIFY(PARAM...) #PARAM
#define RETURN_IF_NULL(PARAM, ret_value) \
if ((PARAM) == nullptr) { \
LOGE("Output parameter |" STRINGIFY(PARAM) "| not provided"); \
return ret_value; \
}
#define RETURN_IF_UNINITIALIZED(ret_value) \
if (!IsInitialized()) { \
LOGE("Crypto session is not initialized"); \
return ret_value; \
}
#define RETURN_IF_NOT_OPEN(ret_value) \
if (!open_) { \
LOGE("Crypto session is not open"); \
return ret_value; \
}
namespace wvcdm {
namespace {
constexpr size_t KiB = 1024;
constexpr size_t MiB = 1024 * 1024;
constexpr uint32_t kRsaSignatureLength = 256;
constexpr size_t kEstimatedInitialUsageTableHeader = 40;
// Constants and utility objects relating to OEM Certificates
constexpr const char* kWidevineSystemIdExtensionOid = "1.3.6.1.4.1.11129.4.1.1";
constexpr int kMaxTerminateCountDown = 5;
const std::string kStringNotAvailable = "NA";
// Constants relating to OEMCrypto resource rating tiers. These tables are
// ordered by resource rating tier from lowest to highest. These should be
// updated whenever the supported range of resource rating tiers changes.
constexpr size_t kMaxSubsampleRegionSizes[] = {
100 * KiB, // Tier 1 - Low
500 * KiB, // Tier 2 - Medium
1 * MiB, // Tier 3 - High
4 * MiB, // Tier 4 - Very High
};
// The +1 in this calculation is because the bounds RESOURCE_RATING_TIER_MAX and
// RESOURCE_RATING_TIER_MIN are inclusive.
static_assert(ArraySize(kMaxSubsampleRegionSizes) ==
RESOURCE_RATING_TIER_MAX - RESOURCE_RATING_TIER_MIN + 1,
"The kMaxSubsampleRegionSizes table needs to be updated to "
"reflect the supported range of resource rating tiers.");
constexpr size_t kDefaultMaxSubsampleRegionSize = kMaxSubsampleRegionSizes[0];
// This maps a few common OEMCryptoResult to CdmResponseType. Many mappings
// are not universal but are OEMCrypto method specific. Those will be
// specified in the CryptoSession method rather than here.
CdmResponseType MapOEMCryptoResult(OEMCryptoResult result,
CdmResponseType default_status,
const char* crypto_session_method) {
if (result != OEMCrypto_SUCCESS) {
LOGE("Mapping OEMCrypto result: crypto_session_method = %s, result = %d",
crypto_session_method ? crypto_session_method : "N/A",
static_cast<int>(result));
}
switch (result) {
case OEMCrypto_SUCCESS:
return NO_ERROR;
case OEMCrypto_ERROR_NOT_IMPLEMENTED:
return NOT_IMPLEMENTED_ERROR;
case OEMCrypto_ERROR_TOO_MANY_SESSIONS:
return INSUFFICIENT_CRYPTO_RESOURCES;
case OEMCrypto_ERROR_SESSION_LOST_STATE:
return SESSION_LOST_STATE_ERROR;
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
return SYSTEM_INVALIDATED_ERROR;
default:
return default_status;
}
}
void AdvanceDestBuffer(OEMCrypto_DestBufferDesc* dest_buffer, size_t bytes) {
switch (dest_buffer->type) {
case OEMCrypto_BufferType_Clear:
dest_buffer->buffer.clear.address += bytes;
dest_buffer->buffer.clear.address_length -= bytes;
return;
case OEMCrypto_BufferType_Secure:
dest_buffer->buffer.secure.offset += bytes;
return;
case OEMCrypto_BufferType_Direct:
// Nothing to do for this buffer type.
return;
}
LOGE("Unrecognized OEMCryptoBufferType %u - doing nothing",
dest_buffer->type);
}
} // namespace
shared_mutex CryptoSession::static_field_mutex_;
shared_mutex CryptoSession::oem_crypto_mutex_;
bool CryptoSession::initialized_ = false;
int CryptoSession::session_count_ = 0;
int CryptoSession::termination_counter_ = 0;
UsageTableHeader* CryptoSession::usage_table_header_l1_ = nullptr;
UsageTableHeader* CryptoSession::usage_table_header_l3_ = nullptr;
std::atomic<uint64_t> CryptoSession::request_id_index_source_(0);
size_t GetOffset(std::string message, std::string field) {
size_t pos = message.find(field);
if (pos == std::string::npos) {
LOGE("Cannot find the |field| offset in message: field = %s",
field.c_str());
pos = 0;
}
return pos;
}
OEMCrypto_Substring GetSubstring(const std::string& message,
const std::string& field, bool set_zero) {
OEMCrypto_Substring substring;
if (set_zero || field.empty() || message.empty()) {
substring.offset = 0;
substring.length = 0;
} else {
size_t pos = message.find(field);
if (pos == std::string::npos) {
LOGW("Cannot find the |field| substring in message: field = %s",
field.c_str());
substring.offset = 0;
substring.length = 0;
} else {
substring.offset = pos;
substring.length = field.length();
}
}
return substring;
}
void GenerateMacContext(const std::string& input_context,
std::string* deriv_context) {
if (!deriv_context) {
LOGE("Output parameter |deriv_context| not provided");
return;
}
const std::string kSigningKeyLabel = "AUTHENTICATION";
const size_t kSigningKeySizeBits = wvcdm::MAC_KEY_SIZE * 8;
deriv_context->assign(kSigningKeyLabel);
deriv_context->append(1, '\0');
deriv_context->append(input_context);
deriv_context->append(EncodeUint32(kSigningKeySizeBits * 2));
}
void GenerateEncryptContext(const std::string& input_context,
std::string* deriv_context) {
if (!deriv_context) {
LOGE("Output parameter |deriv_context| not provided");
return;
}
const std::string kEncryptionKeyLabel = "ENCRYPTION";
const size_t kEncryptionKeySizeBits = wvcdm::CONTENT_KEY_SIZE * 8;
deriv_context->assign(kEncryptionKeyLabel);
deriv_context->append(1, '\0');
deriv_context->append(input_context);
deriv_context->append(EncodeUint32(kEncryptionKeySizeBits));
}
OEMCryptoCipherMode ToOEMCryptoCipherMode(CdmCipherMode cipher_mode) {
return cipher_mode == kCipherModeCtr ? OEMCrypto_CipherMode_CTR
: OEMCrypto_CipherMode_CBC;
}
CryptoSession::CryptoSession(metrics::CryptoMetrics* metrics)
: metrics_(metrics),
system_id_(-1),
open_(false),
pre_provision_token_type_(kClientTokenUninitialized),
update_usage_table_after_close_session_(false),
is_destination_buffer_type_valid_(false),
requested_security_level_(kLevelDefault),
usage_support_type_(kUnknownUsageSupport),
usage_table_header_(nullptr),
api_version_(0),
max_subsample_region_size_(0) {
assert(metrics);
Init();
life_span_.Start();
}
CryptoSession::~CryptoSession() {
if (open_) {
Close();
}
WithStaticFieldWriteLock("~CryptoSession", [&] {
if (session_count_ > 0) {
--session_count_;
} else {
LOGE("Invalid crypto session count: session_count_ = %d", session_count_);
}
});
TryTerminate();
M_RECORD(metrics_, crypto_session_life_span_, life_span_.AsMs());
}
CdmResponseType CryptoSession::GetProvisioningMethod(
SecurityLevel requested_security_level, CdmClientTokenType* token_type) {
OEMCrypto_ProvisioningMethod method;
WithOecReadLock("GetProvisioningMethod", [&] {
method = OEMCrypto_GetProvisioningMethod(requested_security_level);
});
metrics_->oemcrypto_provisioning_method_.Record(method);
CdmClientTokenType type;
switch (method) {
case OEMCrypto_OEMCertificate:
type = kClientTokenOemCert;
break;
case OEMCrypto_Keybox:
type = kClientTokenKeybox;
break;
case OEMCrypto_DrmCertificate:
type = kClientTokenDrmCert;
break;
case OEMCrypto_ProvisioningError:
default:
LOGE("OEMCrypto_GetProvisioningMethod failed: method = %d",
static_cast<int>(method));
metrics_->oemcrypto_provisioning_method_.SetError(method);
return GET_PROVISIONING_METHOD_ERROR;
}
*token_type = type;
return NO_ERROR;
}
void CryptoSession::Init() {
LOGV("Initializing crypto session");
bool initialized = false;
WithStaticFieldWriteLock("Init", [&] {
session_count_ += 1;
if (!initialized_) {
std::string sandbox_id;
OEMCryptoResult sts;
WithOecWriteLock("Init", [&] {
if (Properties::GetSandboxId(&sandbox_id) && !sandbox_id.empty()) {
sts = OEMCrypto_SetSandbox(
reinterpret_cast<const uint8_t*>(sandbox_id.c_str()),
sandbox_id.length());
metrics_->oemcrypto_set_sandbox_.Record(sandbox_id);
}
M_TIME(sts = OEMCrypto_Initialize(), metrics_, oemcrypto_initialize_,
sts);
});
if (OEMCrypto_SUCCESS != sts) {
LOGE("OEMCrypto_Initialize failed: status = %d", static_cast<int>(sts));
return;
}
termination_counter_ = Properties::delay_oem_crypto_termination()
? kMaxTerminateCountDown
: 0;
initialized_ = true;
initialized = true;
}
});
if (initialized) {
uint32_t version;
std::string api_version =
CryptoSession::GetApiVersion(kLevelDefault, &version)
? std::to_string(version)
: kStringNotAvailable;
std::string api_minor_version =
CryptoSession::GetApiMinorVersion(kLevelDefault, &version)
? std::to_string(version)
: kStringNotAvailable;
LOGD("OEMCrypto version (default security level): %s.%s",
api_version.c_str(), api_minor_version.c_str());
api_version = CryptoSession::GetApiVersion(kLevel3, &version)
? std::to_string(version)
: kStringNotAvailable;
api_minor_version = CryptoSession::GetApiMinorVersion(kLevel3, &version)
? std::to_string(version)
: kStringNotAvailable;
LOGD("OEMCrypto version (L3 security level): %s.%s", api_version.c_str(),
api_minor_version.c_str());
}
}
bool CryptoSession::TryTerminate() {
LOGV("Terminating crypto session");
WithStaticFieldWriteLock("TryTerminate", [&] {
LOGV(
"Terminating crypto session: initialized_ = %s, session_count_ = %d, "
"termination_counter_ = %d",
initialized_ ? "true" : "false", session_count_, termination_counter_);
if (termination_counter_ > 0) {
--termination_counter_;
}
if (session_count_ > 0 || termination_counter_ > 0 || !initialized_)
return false;
OEMCryptoResult sts;
WithOecWriteLock("Terminate", [&] { sts = OEMCrypto_Terminate(); });
if (OEMCrypto_SUCCESS != sts) {
LOGE("OEMCrypto_Terminate failed: status = %d", static_cast<int>(sts));
}
if (usage_table_header_l1_ != nullptr) {
delete usage_table_header_l1_;
usage_table_header_l1_ = nullptr;
}
if (usage_table_header_l3_ != nullptr) {
delete usage_table_header_l3_;
usage_table_header_l3_ = nullptr;
}
initialized_ = false;
return true;
});
return true;
}
void CryptoSession::DisableDelayedTermination() {
LOGV("Disable delayed termination");
WithStaticFieldWriteLock("DisableDelayedTermination",
[&] { termination_counter_ = 0; });
}
CdmResponseType CryptoSession::GetTokenFromKeybox(std::string* token) {
RETURN_IF_NULL(token, PARAMETER_NULL);
std::string temp_buffer(KEYBOX_KEY_DATA_SIZE, '\0');
size_t buf_size = temp_buffer.size();
uint8_t* buf = reinterpret_cast<uint8_t*>(&temp_buffer[0]);
OEMCryptoResult status;
WithOecReadLock("GetTokenFromKeybox", [&] {
M_TIME(status =
OEMCrypto_GetKeyData(buf, &buf_size, requested_security_level_),
metrics_, oemcrypto_get_key_data_, status,
metrics::Pow2Bucket(buf_size));
});
if (OEMCrypto_SUCCESS == status) {
token->swap(temp_buffer);
}
return MapOEMCryptoResult(status, GET_TOKEN_FROM_KEYBOX_ERROR,
"GetTokenFromKeybox");
}
CdmResponseType CryptoSession::GetTokenFromOemCert(std::string* token) {
RETURN_IF_NULL(token, PARAMETER_NULL);
OEMCryptoResult status;
if (!oem_token_.empty()) {
token->assign(oem_token_);
return NO_ERROR;
}
std::string temp_buffer(CERTIFICATE_DATA_SIZE, '\0');
bool retrying = false;
while (true) {
size_t buf_size = temp_buffer.size();
uint8_t* buf = reinterpret_cast<uint8_t*>(&temp_buffer[0]);
WithOecSessionLock("GetTokenFromOemCert", [&] {
status = OEMCrypto_GetOEMPublicCertificate(buf, &buf_size,
requested_security_level_);
});
metrics_->oemcrypto_get_oem_public_certificate_.Increment(status);
if (OEMCrypto_SUCCESS == status) {
temp_buffer.resize(buf_size);
oem_token_.assign(temp_buffer);
token->assign(temp_buffer);
return NO_ERROR;
}
if (status == OEMCrypto_ERROR_SHORT_BUFFER && !retrying) {
temp_buffer.resize(buf_size);
retrying = true;
continue;
}
return MapOEMCryptoResult(status, GET_TOKEN_FROM_OEM_CERT_ERROR,
"GetTokenFromOemCert");
}
}
CdmResponseType CryptoSession::GetProvisioningToken(std::string* token) {
if (token == nullptr) {
metrics_->crypto_session_get_token_.Increment(PARAMETER_NULL);
RETURN_IF_NULL(token, PARAMETER_NULL);
}
if (!IsInitialized()) {
metrics_->crypto_session_get_token_.Increment(
CRYPTO_SESSION_NOT_INITIALIZED);
return CRYPTO_SESSION_NOT_INITIALIZED;
}
CdmResponseType status = UNKNOWN_CLIENT_TOKEN_TYPE;
if (pre_provision_token_type_ == kClientTokenKeybox) {
status = GetTokenFromKeybox(token);
} else if (pre_provision_token_type_ == kClientTokenOemCert) {
status = GetTokenFromOemCert(token);
}
metrics_->crypto_session_get_token_.Increment(status);
return status;
}
CdmSecurityLevel CryptoSession::GetSecurityLevel() {
LOGV("Getting security level");
RETURN_IF_NOT_OPEN(kSecurityLevelUninitialized);
return GetSecurityLevel(requested_security_level_);
}
CdmSecurityLevel CryptoSession::GetSecurityLevel(
SecurityLevel requested_level) {
LOGV("Getting security level: requested_level = %d",
static_cast<int>(requested_level));
RETURN_IF_UNINITIALIZED(kSecurityLevelUninitialized);
std::string security_level;
WithOecReadLock("GetSecurityLevel", [&] {
security_level = OEMCrypto_SecurityLevel(requested_level);
});
if ((security_level.size() != 2) || (security_level.at(0) != 'L')) {
return kSecurityLevelUnknown;
}
CdmSecurityLevel cdm_security_level;
switch (security_level.at(1)) {
case '1':
cdm_security_level = kSecurityLevelL1;
break;
case '2':
cdm_security_level = kSecurityLevelL2;
break;
case '3':
cdm_security_level = kSecurityLevelL3;
break;
default:
cdm_security_level = kSecurityLevelUnknown;
break;
}
return cdm_security_level;
}
CdmResponseType CryptoSession::GetInternalDeviceUniqueId(
std::string* device_id) {
RETURN_IF_NULL(device_id, PARAMETER_NULL);
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
std::vector<uint8_t> id;
size_t id_length = 32;
id.resize(id_length);
OEMCryptoResult sts;
WithOecReadLock("GetInternalDeviceUniqueId Attempt 1", [&] {
sts = OEMCrypto_GetDeviceID(&id[0], &id_length, requested_security_level_);
});
// Increment the count of times this method was called.
metrics_->oemcrypto_get_device_id_.Increment(sts);
if (sts == OEMCrypto_ERROR_SHORT_BUFFER) {
id.resize(id_length);
WithOecReadLock("GetInternalDeviceUniqueId Attempt 2", [&] {
sts =
OEMCrypto_GetDeviceID(&id[0], &id_length, requested_security_level_);
});
metrics_->oemcrypto_get_device_id_.Increment(sts);
}
if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED &&
pre_provision_token_type_ == kClientTokenOemCert) {
return GetTokenFromOemCert(device_id);
} else {
// Either the authentication root is a keybox or the device has transitioned
// to using OEMCerts.
// OEMCryptos, like the Level 3, that transition from Provisioning 2.0 to
// 3.0 would have a new device ID, which would affect SPOID calculation.
// In order to resolve this, we use OEMCrypto_GetDeviceID if it is
// implemented, so the OEMCrypto can continue to report the same device ID.
if (sts == OEMCrypto_SUCCESS) {
device_id->assign(reinterpret_cast<char*>(&id[0]), id_length);
}
return MapOEMCryptoResult(sts, GET_DEVICE_ID_ERROR,
"GetInternalDeviceUniqueId");
}
}
CdmResponseType CryptoSession::GetExternalDeviceUniqueId(
std::string* device_id) {
RETURN_IF_NULL(device_id, PARAMETER_NULL);
std::string temp;
CdmResponseType status = GetInternalDeviceUniqueId(&temp);
if (status != NO_ERROR) return status;
size_t id_length = 0;
OEMCryptoResult sts;
WithOecReadLock("GetExternalDeviceUniqueId", [&] {
sts = OEMCrypto_GetDeviceID(nullptr, &id_length, requested_security_level_);
});
metrics_->oemcrypto_get_device_id_.Increment(sts);
if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED &&
pre_provision_token_type_ == kClientTokenOemCert) {
// To keep the size of the value passed back to the application down, hash
// the large OEM Public Cert to a smaller value.
temp = Sha256Hash(temp);
}
*device_id = temp;
return NO_ERROR;
}
bool CryptoSession::GetApiVersion(uint32_t* version) {
LOGV("Getting API version");
RETURN_IF_NOT_OPEN(false);
return GetApiVersion(requested_security_level_, version);
}
bool CryptoSession::GetApiVersion(SecurityLevel security_level,
uint32_t* version) {
LOGV("Getting API version: security_level = %d",
static_cast<int>(security_level));
if (!version) {
LOGE("Output parameter |version| not provided");
return false;
}
RETURN_IF_UNINITIALIZED(false);
WithOecReadLock("GetApiVersion",
[&] { *version = OEMCrypto_APIVersion(security_level); });
// Record the version into the metrics.
metrics_->oemcrypto_api_version_.Record(*version);
return true;
}
bool CryptoSession::GetApiMinorVersion(SecurityLevel security_level,
uint32_t* minor_version) {
LOGV("Getting API minor version: security_level = %d",
static_cast<int>(security_level));
if (!minor_version) {
LOGE("Output parameter |minor_version| not provided");
return false;
}
RETURN_IF_UNINITIALIZED(false);
WithOecReadLock("GetApiMinorVersion", [&] {
*minor_version = OEMCrypto_MinorAPIVersion(security_level);
});
// Record the minor version into the metrics.
metrics_->oemcrypto_minor_api_version_.Record(*minor_version);
return true;
}
bool CryptoSession::GetSystemId(uint32_t* system_id) {
RETURN_IF_NULL(system_id, false);
RETURN_IF_UNINITIALIZED(false);
RETURN_IF_NOT_OPEN(false);
*system_id = system_id_;
return true;
}
// This method gets the system id from the keybox key data.
// This method assumes that OEMCrypto has been initialized before making this
// call.
CdmResponseType CryptoSession::GetSystemIdInternal(uint32_t* system_id) {
RETURN_IF_NULL(system_id, PARAMETER_NULL);
if (pre_provision_token_type_ == kClientTokenKeybox) {
std::string token;
CdmResponseType status = GetTokenFromKeybox(&token);
if (status != NO_ERROR) return status;
if (token.size() < 2 * sizeof(uint32_t)) {
LOGE("Keybox token size too small: token_size = %zu", token.size());
return KEYBOX_TOKEN_TOO_SHORT;
}
// Decode 32-bit int encoded as network-byte-order byte array starting at
// index 4.
uint32_t* id = reinterpret_cast<uint32_t*>(&token[4]);
*system_id = ntohl(*id);
return NO_ERROR;
} else if (pre_provision_token_type_ == kClientTokenOemCert) {
// Get the OEM Cert
std::string oem_cert;
CdmResponseType status = GetTokenFromOemCert(&oem_cert);
if (status != NO_ERROR) return status;
if (!ExtractSystemIdFromOemCert(oem_cert, system_id))
return EXTRACT_SYSTEM_ID_FROM_OEM_CERT_ERROR;
return NO_ERROR;
// TODO(blueeyes): Support loading the system id from a pre-provisioned
// Drm certificate.
} else if (pre_provision_token_type_ == kClientTokenDrmCert) {
return NO_ERROR;
} else {
LOGE("Unsupported pre-provision token type: %d",
static_cast<int>(pre_provision_token_type_));
return UNKNOWN_CLIENT_TOKEN_TYPE;
}
}
bool CryptoSession::ExtractSystemIdFromOemCert(const std::string& oem_cert,
uint32_t* system_id) {
return ExtractExtensionValueFromCertificate(
oem_cert, kWidevineSystemIdExtensionOid, /* cert_index */ 1, system_id);
}
CdmResponseType CryptoSession::GetProvisioningId(std::string* provisioning_id) {
RETURN_IF_NULL(provisioning_id, PARAMETER_NULL);
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
if (pre_provision_token_type_ == kClientTokenOemCert) {
// OEM Cert devices have no provisioning-unique ID embedded in them, so we
// synthesize one by using the External Device-Unique ID and inverting all
// the bits.
CdmResponseType status = GetExternalDeviceUniqueId(provisioning_id);
if (status != NO_ERROR) return status;
for (size_t i = 0; i < provisioning_id->size(); ++i) {
char value = (*provisioning_id)[i];
(*provisioning_id)[i] = ~value;
}
return NO_ERROR;
} else if (pre_provision_token_type_ == kClientTokenKeybox) {
std::string token;
CdmResponseType status = GetTokenFromKeybox(&token);
if (status != NO_ERROR) return status;
if (token.size() < 24) {
LOGE("Keybox token size too small: %zu", token.size());
return KEYBOX_TOKEN_TOO_SHORT;
}
provisioning_id->assign(reinterpret_cast<char*>(&token[8]), 16);
return NO_ERROR;
} else {
LOGE("Unsupported pre-provision token type: %d",
static_cast<int>(pre_provision_token_type_));
return UNKNOWN_CLIENT_TOKEN_TYPE;
}
}
uint8_t CryptoSession::GetSecurityPatchLevel() {
uint8_t patch;
WithOecReadLock("GetSecurityPatchLevel", [&] {
patch = OEMCrypto_Security_Patch_Level(requested_security_level_);
});
metrics_->oemcrypto_security_patch_level_.Record(patch);
return patch;
}
CdmResponseType CryptoSession::Open(SecurityLevel requested_security_level) {
LOGD("Opening crypto session: requested_security_level = %s",
requested_security_level == kLevel3
? QUERY_VALUE_SECURITY_LEVEL_L3.c_str()
: QUERY_VALUE_SECURITY_LEVEL_DEFAULT.c_str());
RETURN_IF_UNINITIALIZED(UNKNOWN_ERROR);
if (open_) return NO_ERROR;
CdmResponseType result = GetProvisioningMethod(requested_security_level,
&pre_provision_token_type_);
if (result != NO_ERROR) return result;
OEMCrypto_SESSION sid;
requested_security_level_ = requested_security_level;
OEMCryptoResult sts;
WithOecWriteLock("Open() calling OEMCrypto_OpenSession", [&] {
sts = OEMCrypto_OpenSession(&sid, requested_security_level);
});
if (sts != OEMCrypto_SUCCESS) {
WithStaticFieldReadLock(
"Open() reporting OEMCrypto_OpenSession Failure", [&] {
LOGE(
"OEMCrypto_Open failed: status = %d, session_count_ = %d,"
" initialized_ = %s",
static_cast<int>(sts), session_count_,
initialized_ ? "true" : "false");
});
return MapOEMCryptoResult(sts, OPEN_CRYPTO_SESSION_ERROR, "Open");
}
WithStaticFieldWriteLock("Open() termination_counter", [&] {
termination_counter_ =
Properties::delay_oem_crypto_termination() ? kMaxTerminateCountDown : 0;
});
oec_session_id_ = static_cast<CryptoSessionId>(sid);
LOGV("Opened session: id = %u", oec_session_id_);
open_ = true;
// Get System ID and save it.
result = GetSystemIdInternal(&system_id_);
if (result == NO_ERROR) {
metrics_->crypto_session_system_id_.Record(system_id_);
} else {
LOGE("Failed to fetch system ID");
metrics_->crypto_session_system_id_.SetError(result);
return result;
}
// Set up request ID
uint64_t request_id_base;
OEMCryptoResult random_sts;
WithOecReadLock("Open() calling OEMCrypto_GetRandom", [&] {
random_sts = OEMCrypto_GetRandom(
reinterpret_cast<uint8_t*>(&request_id_base), sizeof(request_id_base));
});
metrics_->oemcrypto_get_random_.Increment(random_sts);
uint64_t request_id_index =
request_id_index_source_.fetch_add(1, std::memory_order_relaxed);
request_id_ = HexEncode(reinterpret_cast<uint8_t*>(&request_id_base),
sizeof(request_id_base)) +
HexEncode(reinterpret_cast<uint8_t*>(&request_id_index),
sizeof(request_id_index));
// Initialize key session
WithOecSessionLock("Open() calling key_session_.reset()", [&] {
key_session_.reset(new ContentKeySession(oec_session_id_, metrics_));
});
// Set up Usage Table support
//
// This MUST be the last thing in the function because it contains the
// successful return paths of the function. It will attempt to initialize
// usage tables. However, whether they are employed is determined by
// information in the license, which will not be received until later. In case
// usage tables are not even needed, initialization errors here are ignored by
// returning successfully.
//
// TODO(b/141350978): Refactor this code so that it does not have this
// problem.
if (!GetApiVersion(&api_version_)) {
LOGE("Failed to get API version");
return USAGE_SUPPORT_GET_API_FAILED;
}
CdmUsageSupportType usage_support_type;
result = GetUsageSupportType(&usage_support_type);
if (result == NO_ERROR) {
metrics_->oemcrypto_usage_table_support_.Record(usage_support_type);
if (usage_support_type == kUsageEntrySupport) {
CdmSecurityLevel security_level = GetSecurityLevel();
if (security_level == kSecurityLevelL1 ||
security_level == kSecurityLevelL3) {
// This block cannot use |WithStaticFieldWriteLock| because it needs
// to unlock the lock partway through.
LOGV("Static field write lock: Open() initializing usage table");
std::unique_lock<shared_mutex> auto_lock(static_field_mutex_);
UsageTableHeader** header = security_level == kSecurityLevelL1
? &usage_table_header_l1_
: &usage_table_header_l3_;
if (*header == nullptr) {
*header = new UsageTableHeader();
// Ignore errors since we do not know when a session is opened,
// if it is intended to be used for offline/usage session related
// or otherwise.
auto_lock.unlock();
bool is_usage_table_header_inited =
(*header)->Init(security_level, this);
auto_lock.lock();
if (!is_usage_table_header_inited) {
delete *header;
*header = nullptr;
usage_table_header_ = nullptr;
return NO_ERROR;
}
}
usage_table_header_ = *header;
metrics_->usage_table_header_initial_size_.Record((*header)->size());
} // End |static_field_mutex_| block.
}
} else {
metrics_->oemcrypto_usage_table_support_.SetError(result);
}
// Do not add logic after this point as it may never get exercised. In the
// event of errors initializing the usage tables, the code will return early,
// never reaching this point. See the comment above the "Set up Usage Table
// support" section for details.
//
// TODO(b/139973602): Refactor this code.
return NO_ERROR;
}
void CryptoSession::Close() {
LOGV("Closing crypto session: id = %u, open = %s", oec_session_id_,
open_ ? "true" : "false");
if (!open_) return;
OEMCryptoResult close_sts;
WithOecWriteLock(
"Close", [&] { close_sts = OEMCrypto_CloseSession(oec_session_id_); });
metrics_->oemcrypto_close_session_.Increment(close_sts);
if (OEMCrypto_SUCCESS == close_sts) open_ = false;
}
CdmResponseType CryptoSession::PrepareAndSignLicenseRequest(
const std::string& message, std::string* core_message,
std::string* signature) {
LOGV("Preparing and signing license request: id = %u", oec_session_id_);
RETURN_IF_NULL(signature, PARAMETER_NULL);
RETURN_IF_NULL(core_message, PARAMETER_NULL);
OEMCryptoResult sts;
size_t signature_length = 0;
size_t core_message_length = 0;
*core_message = "";
std::string combined_message = *core_message + message;
// First call is intended to determine the required size of the
// output buffers.
WithOecSessionLock("PrepareAndSignLicenseRequest", [&] {
M_TIME(sts = OEMCrypto_PrepAndSignLicenseRequest(
oec_session_id_,
reinterpret_cast<uint8_t*>(
const_cast<char*>(combined_message.data())),
combined_message.size(), &core_message_length, nullptr,
&signature_length),
metrics_, oemcrypto_prep_and_sign_license_request_, sts);
});
if (OEMCrypto_ERROR_SHORT_BUFFER != sts) {
return MapOEMCryptoResult(sts, GENERATE_SIGNATURE_ERROR,
"PrepareAndSignLicenseRequest");
}
// Resize.
core_message->resize(core_message_length);
signature->resize(signature_length);
combined_message = *core_message + message;
WithOecSessionLock("PrepareAndSignLicenseRequest", [&] {
M_TIME(sts = OEMCrypto_PrepAndSignLicenseRequest(
oec_session_id_,
reinterpret_cast<uint8_t*>(
const_cast<char*>(combined_message.data())),
combined_message.size(), &core_message_length,
reinterpret_cast<uint8_t*>(const_cast<char*>(signature->data())),
&signature_length),
metrics_, oemcrypto_prep_and_sign_license_request_, sts);
});
if (OEMCrypto_SUCCESS == sts) {
signature->resize(signature_length);
*core_message = std::move(combined_message);
// Truncate combined message to only contain the core message.
core_message->resize(core_message_length);
return NO_ERROR;
}
return MapOEMCryptoResult(sts, GENERATE_SIGNATURE_ERROR,
"PrepareAndSignLicenseRequest");
}
CdmResponseType CryptoSession::LoadKeys(
const std::string& message, const std::string& signature,
const std::string& mac_key_iv, const std::string& mac_key,
const std::vector<CryptoKey>& keys,
const std::string& provider_session_token,
const std::string& srm_requirement, CdmLicenseKeyType key_type) {
LOGV("Loading keys: id = %u", oec_session_id_);
OEMCryptoResult sts;
WithOecSessionLock("LoadKeys", [&] {
if (key_type == kLicenseKeyTypeEntitlement &&
key_session_->Type() != KeySession::kEntitlement) {
key_session_.reset(new EntitlementKeySession(oec_session_id_, metrics_));
}
LOGV("Loading key: id = %u", oec_session_id_);
sts = key_session_->LoadKeys(message, signature, mac_key_iv, mac_key, keys,
provider_session_token, srm_requirement);
});
if (sts != OEMCrypto_SUCCESS) {
LOGE("OEMCrypto_LoadKeys failed: status = %d", static_cast<int>(sts));
}
switch (sts) {
case OEMCrypto_SUCCESS:
if (!provider_session_token.empty())
update_usage_table_after_close_session_ = true;
return KEY_ADDED;
case OEMCrypto_ERROR_TOO_MANY_KEYS:
return INSUFFICIENT_CRYPTO_RESOURCES_4;
case OEMCrypto_ERROR_USAGE_TABLE_UNRECOVERABLE:
// Handle vendor specific error
return NEED_PROVISIONING;
default:
break;
}
return MapOEMCryptoResult(sts, LOAD_KEY_ERROR, "LoadKeys");
}
CdmResponseType CryptoSession::LoadLicense(const std::string& signed_message,
const std::string& core_message,
const std::string& signature) {
LOGV("Loading license: id = %u", oec_session_id_);
const std::string combined_message = core_message + signed_message;
OEMCryptoResult sts;
WithOecSessionLock("LoadLicense", [&] {
M_TIME(sts = OEMCrypto_LoadLicense(
oec_session_id_,
reinterpret_cast<const uint8_t*>(combined_message.data()),
combined_message.size(), core_message.size(),
reinterpret_cast<const uint8_t*>(signature.data()),
signature.size()),
metrics_, oemcrypto_load_license_, sts);
});
switch (sts) {
case OEMCrypto_SUCCESS:
return KEY_ADDED;
case OEMCrypto_ERROR_BUFFER_TOO_LARGE:
LOGE("LoadLicense buffer too large: size = %zu", combined_message.size());
return LOAD_LICENSE_ERROR;
case OEMCrypto_ERROR_TOO_MANY_KEYS:
LOGE("Too many keys in license");
return INSUFFICIENT_CRYPTO_RESOURCES_4;
default:
break;
}
return MapOEMCryptoResult(sts, LOAD_LICENSE_ERROR, "LoadLicense");
}
CdmResponseType CryptoSession::PrepareAndSignRenewalRequest(
const std::string& message, std::string* core_message,
std::string* signature) {
LOGV("Preparing and signing renewal request: id = %u", oec_session_id_);
if (signature == nullptr) {
LOGE("Output parameter |signature| not provided");
return PARAMETER_NULL;
}
if (core_message == nullptr) {
LOGE("Output parameter |core_message| not provided");
return PARAMETER_NULL;
}
OEMCryptoResult sts;
size_t signature_length = 0;
size_t core_message_length = 0;
*core_message = "";
std::string combined_message = *core_message + message;
// First call is intended to determine the required size of the
// output buffers.
WithOecSessionLock("PrepareAndSignRenewalRequest", [&] {
M_TIME(sts = OEMCrypto_PrepAndSignRenewalRequest(
oec_session_id_,
reinterpret_cast<uint8_t*>(
const_cast<char*>(combined_message.data())),
combined_message.size(), &core_message_length, nullptr,
&signature_length),
metrics_, oemcrypto_prep_and_sign_renewal_request_, sts);
});
if (OEMCrypto_ERROR_SHORT_BUFFER != sts) {
return MapOEMCryptoResult(sts, GENERATE_SIGNATURE_ERROR,
"PrepareAndSignRenewalRequest");
}
// Resize.
core_message->resize(core_message_length);
signature->resize(signature_length);
combined_message = *core_message + message;
WithOecSessionLock("PrepareAndSignRenewalRequest", [&] {
M_TIME(sts = OEMCrypto_PrepAndSignRenewalRequest(
oec_session_id_,
reinterpret_cast<uint8_t*>(
const_cast<char*>(combined_message.data())),
combined_message.size(), &core_message_length,
reinterpret_cast<uint8_t*>(const_cast<char*>(signature->data())),
&signature_length),
metrics_, oemcrypto_prep_and_sign_renewal_request_, sts);
});
if (OEMCrypto_SUCCESS == sts) {
signature->resize(signature_length);
*core_message = std::move(combined_message);
// Truncate combined message to only contain the core message.
core_message->resize(core_message_length);
return NO_ERROR;
}
return MapOEMCryptoResult(sts, GENERATE_SIGNATURE_ERROR,
"PrepareAndSignRenewalRequest");
}
CdmResponseType CryptoSession::RefreshKeys(
const std::string& message, const std::string& signature,
const std::vector<CryptoKey>& key_array) {
const uint8_t* msg = reinterpret_cast<const uint8_t*>(message.data());
std::vector<OEMCrypto_KeyRefreshObject> load_key_array(key_array.size());
for (size_t i = 0; i < key_array.size(); ++i) {
const CryptoKey* ki = &key_array[i];
OEMCrypto_KeyRefreshObject* ko = &load_key_array[i];
ko->key_id = GetSubstring(message, ki->key_id());
bool has_key_control = ki->HasKeyControl();
ko->key_control_iv =
GetSubstring(message, ki->key_control_iv(), !has_key_control);
ko->key_control =
GetSubstring(message, ki->key_control(), !has_key_control);
}
LOGV("Refreshing keys: id = %u", oec_session_id_);
OEMCryptoResult refresh_sts;
WithOecSessionLock("RefreshKeys", [&] {
M_TIME(refresh_sts = OEMCrypto_RefreshKeys(
oec_session_id_, msg, message.size(),
reinterpret_cast<const uint8_t*>(signature.data()),
signature.size(), key_array.size(), &load_key_array[0]),
metrics_, oemcrypto_refresh_keys_, refresh_sts);
});
if (refresh_sts == OEMCrypto_SUCCESS) return KEY_ADDED;
return MapOEMCryptoResult(refresh_sts, REFRESH_KEYS_ERROR, "RefreshKeys");
}
CdmResponseType CryptoSession::LoadRenewal(const std::string& signed_message,
const std::string& core_message,
const std::string& signature) {
LOGV("Loading license renewal: id = %u", oec_session_id_);
const std::string combined_message = core_message + signed_message;
OEMCryptoResult sts;
WithOecSessionLock("LoadRenewal", [&] {
M_TIME(sts = OEMCrypto_LoadRenewal(
oec_session_id_,
reinterpret_cast<const uint8_t*>(combined_message.data()),
combined_message.size(), core_message.size(),
reinterpret_cast<const uint8_t*>(signature.data()),
signature.size()),
metrics_, oemcrypto_load_renewal_, sts);
});
if (sts == OEMCrypto_SUCCESS) {
return KEY_ADDED;
}
if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE) {
LOGE("Buffer too large: size = %zu", combined_message.size());
return LOAD_RENEWAL_ERROR;
}
return MapOEMCryptoResult(sts, LOAD_RENEWAL_ERROR, "LoadRenewal");
}
CdmResponseType CryptoSession::PrepareAndSignProvisioningRequest(
const std::string& message, std::string* core_message,
std::string* signature) {
LOGV("Preparing and signing provisioning request: id = %u", oec_session_id_);
if (signature == nullptr) {
LOGE("Output parameter |signature| not provided");
return PARAMETER_NULL;
}
if (core_message == nullptr) {
LOGE("Output parameter |core_message| not provided");
return PARAMETER_NULL;
}
if (pre_provision_token_type_ == kClientTokenKeybox) {
const CdmResponseType status = GenerateDerivedKeys(message);
if (status != NO_ERROR) return status;
} else if (pre_provision_token_type_ == kClientTokenOemCert) {
const OEMCryptoResult status = OEMCrypto_LoadOEMPrivateKey(oec_session_id_);
if (status != OEMCrypto_SUCCESS) {
return MapOEMCryptoResult(status, GET_TOKEN_FROM_OEM_CERT_ERROR,
"GetTokenFromOemCert");
}
} else {
LOGE("Unknown method %d", pre_provision_token_type_);
return UNKNOWN_CLIENT_TOKEN_TYPE;
}
OEMCryptoResult sts;
size_t signature_length = 0;
size_t core_message_length = 0;
*core_message = "";
std::string combined_message = *core_message + message;
// First call is intended to determine the required size of the
// output buffers.
WithOecSessionLock("PrepareAndSignProvisioningRequest", [&] {
M_TIME(sts = OEMCrypto_PrepAndSignProvisioningRequest(
oec_session_id_,
reinterpret_cast<uint8_t*>(
const_cast<char*>(combined_message.data())),
combined_message.size(), &core_message_length, nullptr,
&signature_length),
metrics_, oemcrypto_prep_and_sign_provisioning_request_, sts);
});
if (OEMCrypto_ERROR_SHORT_BUFFER != sts) {
return MapOEMCryptoResult(sts, GENERATE_SIGNATURE_ERROR,
"PrepareAndSignProvisioningRequest");
}
// Resize.
core_message->resize(core_message_length);
signature->resize(signature_length);
combined_message = *core_message + message;
WithOecSessionLock("PrepareAndSignProvisioningRequest", [&] {
M_TIME(sts = OEMCrypto_PrepAndSignProvisioningRequest(
oec_session_id_,
reinterpret_cast<uint8_t*>(
const_cast<char*>(combined_message.data())),
combined_message.size(), &core_message_length,
reinterpret_cast<uint8_t*>(const_cast<char*>(signature->data())),
&signature_length),
metrics_, oemcrypto_prep_and_sign_provisioning_request_, sts);
});
if (OEMCrypto_SUCCESS == sts) {
signature->resize(signature_length);
*core_message = std::move(combined_message);
// Truncate combined message to only contain the core message.
core_message->resize(core_message_length);
return NO_ERROR;
}
return MapOEMCryptoResult(sts, GENERATE_SIGNATURE_ERROR,
"PrepareAndSignProvisioningRequest");
}
CdmResponseType CryptoSession::LoadEntitledContentKeys(
const std::vector<CryptoKey>& key_array) {
OEMCryptoResult sts;
WithOecSessionLock("LoadEntitledContentKeys", [&] {
sts = key_session_->LoadEntitledContentKeys(key_array);
});
switch (sts) {
case OEMCrypto_SUCCESS:
return KEY_ADDED;
case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES:
return INSUFFICIENT_CRYPTO_RESOURCES_6;
case OEMCrypto_ERROR_INVALID_CONTEXT:
return NOT_AN_ENTITLEMENT_SESSION;
case OEMCrypto_KEY_NOT_ENTITLED:
return NO_MATCHING_ENTITLEMENT_KEY;
case OEMCrypto_ERROR_SESSION_LOST_STATE:
return SESSION_LOST_STATE_ERROR;
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
return SYSTEM_INVALIDATED_ERROR;
default:
return LOAD_ENTITLED_CONTENT_KEYS_ERROR;
}
}
CdmResponseType CryptoSession::LoadCertificatePrivateKey(
const std::string& wrapped_key) {
// TODO(b/141655126): Getting the OEM Cert no longer loads the private key.
// Call OEMCrypto_GetOEMPublicCertificate before OEMCrypto_LoadDRMPrivateKey
// so it caches the OEMCrypto Public Key and then throw away result
std::string temp_buffer(CERTIFICATE_DATA_SIZE, '\0');
size_t buf_size = temp_buffer.size();
uint8_t* buf = reinterpret_cast<uint8_t*>(&temp_buffer[0]);
OEMCryptoResult sts;
WithOecSessionLock(
"LoadCertificatePrivateKey() calling OEMCrypto_GetOEMPublicCertificate",
[&] {
sts = OEMCrypto_GetOEMPublicCertificate(buf, &buf_size,
requested_security_level_);
});
metrics_->oemcrypto_get_oem_public_certificate_.Increment(sts);
LOGV("Loading device RSA key: id = %u", oec_session_id_);
// TODO(b/140813486): determine if cert is RSA or ECC.
WithOecSessionLock(
"LoadCertificatePrivateKey() calling OEMCrypto_LoadDRMPrivateKey()", [&] {
M_TIME(sts = OEMCrypto_LoadDRMPrivateKey(
oec_session_id_, OEMCrypto_RSA_Private_Key,
reinterpret_cast<const uint8_t*>(wrapped_key.data()),
wrapped_key.size()),
metrics_, oemcrypto_load_device_rsa_key_, sts);
});
return MapOEMCryptoResult(sts, LOAD_DEVICE_RSA_KEY_ERROR,
"LoadCertificatePrivateKey");
}
// Private.
CdmResponseType CryptoSession::SelectKey(const std::string& key_id,
CdmCipherMode cipher_mode) {
OEMCryptoResult sts;
WithOecSessionLock(
"SelectKey", [&] { sts = key_session_->SelectKey(key_id, cipher_mode); });
switch (sts) {
case OEMCrypto_SUCCESS:
return NO_ERROR;
case OEMCrypto_ERROR_KEY_EXPIRED:
return NEED_KEY;
case OEMCrypto_ERROR_INSUFFICIENT_HDCP:
return INSUFFICIENT_OUTPUT_PROTECTION;
case OEMCrypto_ERROR_ANALOG_OUTPUT:
return ANALOG_OUTPUT_ERROR;
case OEMCrypto_ERROR_INVALID_SESSION:
return INVALID_SESSION_1;
case OEMCrypto_ERROR_NO_DEVICE_KEY:
return NO_DEVICE_KEY_1;
case OEMCrypto_ERROR_NO_CONTENT_KEY:
return NO_CONTENT_KEY_2;
case OEMCrypto_KEY_NOT_LOADED: // obsolete.
return NO_CONTENT_KEY_3;
case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES:
return INSUFFICIENT_CRYPTO_RESOURCES_2;
case OEMCrypto_ERROR_UNKNOWN_FAILURE:
return UNKNOWN_SELECT_KEY_ERROR_1;
case OEMCrypto_ERROR_SESSION_LOST_STATE:
return SESSION_LOST_STATE_ERROR;
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
return SYSTEM_INVALIDATED_ERROR;
case OEMCrypto_ERROR_CONTROL_INVALID:
case OEMCrypto_ERROR_KEYBOX_INVALID:
default:
return UNKNOWN_SELECT_KEY_ERROR_2;
}
}
CdmResponseType CryptoSession::GenerateDerivedKeys(const std::string& message) {
OEMCryptoResult sts;
WithOecSessionLock("GenerateDerivedKeys without session_key",
[&] { sts = key_session_->GenerateDerivedKeys(message); });
return MapOEMCryptoResult(sts, GENERATE_DERIVED_KEYS_ERROR_2,
"GenerateDerivedKeys");
}
CdmResponseType CryptoSession::GenerateDerivedKeys(
const std::string& message, const std::string& session_key) {
OEMCryptoResult sts;
WithOecSessionLock("GenerateDerivedKeys with session_key", [&] {
sts = key_session_->GenerateDerivedKeys(message, session_key);
});
return MapOEMCryptoResult(sts, GENERATE_DERIVED_KEYS_ERROR,
"GenerateDerivedKeys");
}
CdmResponseType CryptoSession::GenerateRsaSignature(const std::string& message,
std::string* signature) {
LOGV("Generating RSA signature: id = %u", oec_session_id_);
RETURN_IF_NULL(signature, PARAMETER_NULL);
OEMCryptoResult sts;
signature->resize(kRsaSignatureLength);
size_t length = signature->size();
// At most two attempts.
// The first attempt may fail due to buffer too short
for (int i = 0; i < 2; ++i) {
WithOecSessionLock("GenerateRsaSignature", [&] {
M_TIME(
sts = OEMCrypto_GenerateRSASignature(
oec_session_id_, reinterpret_cast<const uint8_t*>(message.data()),
message.size(),
reinterpret_cast<uint8_t*>(const_cast<char*>(signature->data())),
&length, kSign_RSASSA_PSS),
metrics_, oemcrypto_generate_rsa_signature_, sts,
metrics::Pow2Bucket(length));
});
if (OEMCrypto_SUCCESS == sts) {
// Trim signature buffer and done
signature->resize(length);
return NO_ERROR;
}
if (OEMCrypto_ERROR_SHORT_BUFFER != sts) {
break;
}
// Retry with proper-sized signature buffer
signature->resize(length);
}
return MapOEMCryptoResult(sts, RSA_SIGNATURE_GENERATION_ERROR,
"OEMCrypto_GenerateRSASignature");
}
size_t CryptoSession::GetMaxSubsampleRegionSize() {
// If we haven't cached the answer yet, fetch it from OEMCrypto.
if (max_subsample_region_size_ == 0) {
uint32_t tier = 0;
if (GetResourceRatingTier(&tier)) {
// Subtract RESOURCE_RATING_TIER_MIN to get a 0-based index into the
// table.
const uint32_t index = tier - RESOURCE_RATING_TIER_MIN;
if (index < ArraySize(kMaxSubsampleRegionSizes)) {
max_subsample_region_size_ = kMaxSubsampleRegionSizes[index];
}
}
// If something went wrong, use the default.
if (max_subsample_region_size_ == 0) {
LOGW("Unable to get maximum subsample region size. Defaulting to %zu.",
kDefaultMaxSubsampleRegionSize);
max_subsample_region_size_ = kDefaultMaxSubsampleRegionSize;
}
}
return max_subsample_region_size_;
}
CdmResponseType CryptoSession::Decrypt(
const CdmDecryptionParametersV16& params) {
if (!is_destination_buffer_type_valid_) {
if (!SetDestinationBufferType()) return UNKNOWN_ERROR;
}
OEMCryptoBufferType output_descriptor_type =
params.is_secure ? destination_buffer_type_ : OEMCrypto_BufferType_Clear;
if (params.is_secure &&
output_descriptor_type == OEMCrypto_BufferType_Clear) {
return SECURE_BUFFER_REQUIRED;
}
if (params.samples.size() == 0) return CANNOT_DECRYPT_ZERO_SAMPLES;
if (std::any_of(std::begin(params.samples), std::end(params.samples),
[](const CdmDecryptionSample& sample) -> bool {
return sample.subsamples.size() == 0;
})) {
return CANNOT_DECRYPT_ZERO_SUBSAMPLES;
}
// Convert all the sample and subsample definitions to OEMCrypto structs.
// This code also caches whether any of the data is protected, to save later
// code the trouble of iterating over all the subsamples to check.
bool is_any_sample_protected = false;
std::vector<OEMCrypto_SampleDescription> oec_samples;
oec_samples.reserve(params.samples.size());
std::vector<std::vector<OEMCrypto_SubSampleDescription>>
oec_subsample_vectors;
oec_subsample_vectors.reserve(params.samples.size());
for (const CdmDecryptionSample& sample : params.samples) {
oec_samples.emplace_back();
OEMCrypto_SampleDescription& oec_sample = oec_samples.back();
// Set up the sample's input buffer
oec_sample.buffers.input_data = sample.encrypt_buffer;
oec_sample.buffers.input_data_length = sample.encrypt_buffer_length;
// Set up the sample's output buffer
OEMCrypto_DestBufferDesc& output_descriptor =
oec_sample.buffers.output_descriptor;
output_descriptor.type = output_descriptor_type;
switch (output_descriptor.type) {
case OEMCrypto_BufferType_Clear:
output_descriptor.buffer.clear.address =
static_cast<uint8_t*>(sample.decrypt_buffer) +
sample.decrypt_buffer_offset;
output_descriptor.buffer.clear.address_length =
sample.decrypt_buffer_size - sample.decrypt_buffer_offset;
break;
case OEMCrypto_BufferType_Secure:
output_descriptor.buffer.secure.handle = sample.decrypt_buffer;
output_descriptor.buffer.secure.offset = sample.decrypt_buffer_offset;
output_descriptor.buffer.secure.handle_length =
sample.decrypt_buffer_size;
break;
case OEMCrypto_BufferType_Direct:
output_descriptor.buffer.direct.is_video = params.is_video;
break;
}
// Convert all the sample's subsamples. This code also tallies the total
// size as a sanity check.
oec_subsample_vectors.emplace_back();
std::vector<OEMCrypto_SubSampleDescription>& oec_subsamples =
oec_subsample_vectors.back();
oec_subsamples.reserve(sample.subsamples.size());
size_t sample_size = 0;
bool is_any_subsample_protected = false;
size_t current_block_offset = 0;
for (const CdmDecryptionSubsample& subsample : sample.subsamples) {
oec_subsamples.push_back(OEMCrypto_SubSampleDescription{
subsample.clear_bytes, subsample.protected_bytes,
0, // subsample_flags
current_block_offset});
is_any_subsample_protected |= (subsample.protected_bytes > 0);
sample_size += subsample.clear_bytes + subsample.protected_bytes;
if (params.cipher_mode == kCipherModeCtr) {
current_block_offset =
(current_block_offset + subsample.protected_bytes) %
kAes128BlockSize;
}
// TODO(b/149524614): This block is not necessary except for
// backwards-compatibility while we are transitioning from the v15 API to
// the v16 API.
if (params.observe_legacy_fields) {
OEMCrypto_SubSampleDescription& oec_subsample = oec_subsamples.back();
oec_subsample.subsample_flags = subsample.flags;
oec_subsample.block_offset = subsample.block_offset;
}
}
is_any_sample_protected |= is_any_subsample_protected;
// TODO(b/149524614): This check is not necessary except for
// backwards-compatibility while we are transitioning from the v15 API to
// the v16 API.
if (!params.observe_legacy_fields) {
// Set the actual subsample_flags now that all the subsamples are
// converted.
oec_subsamples.front().subsample_flags |= OEMCrypto_FirstSubsample;
oec_subsamples.back().subsample_flags |= OEMCrypto_LastSubsample;
}
// Check that the total size is valid
if (sample_size != oec_sample.buffers.input_data_length)
return SAMPLE_AND_SUBSAMPLE_SIZE_MISMATCH;
// Set up the sample's IV
if (is_any_subsample_protected) {
if (sizeof(oec_sample.iv) != sample.iv.size()) return INVALID_IV_SIZE;
memcpy(oec_sample.iv, sample.iv.data(), sizeof(oec_sample.iv));
} else {
memset(oec_sample.iv, 0, sizeof(oec_sample.iv));
}
// Attach the subsamples to the sample description
oec_sample.subsamples = oec_subsamples.data();
oec_sample.subsamples_length = oec_subsamples.size();
}
// Convert the pattern descriptor
OEMCrypto_CENCEncryptPatternDesc oec_pattern{params.pattern.encrypt_blocks,
params.pattern.skip_blocks};
// TODO(b/146581957): Remove this workaround once OEMCrypto treats (0,0) as
// 'cbcs' instead of 'cbc1'.
if (params.cipher_mode == kCipherModeCbc && oec_pattern.encrypt == 0 &&
oec_pattern.skip == 0) {
// (10, 0) is the preferred pattern for decrypting every block in 'cbcs'
oec_pattern.encrypt = 10;
}
// Check if a key needs to be selected
if (is_any_sample_protected) {
CdmResponseType result = SelectKey(params.key_id, params.cipher_mode);
if (result != NO_ERROR) return result;
}
// Perform decrypt
const OEMCryptoResult sts =
DecryptMultipleSamples(oec_samples, params.cipher_mode, oec_pattern);
if (sts != OEMCrypto_SUCCESS && last_decrypt_error_ != sts) {
// Decrypt errors and warnings are only logged when the error code
// changes. This is in anticipation that if an error code is
// returned, then the same error code is likely to be returned in
// the next call. The calling application may make several more
// decrypt requests before the error is handled by the app.
last_decrypt_error_ = sts;
if (sts == OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION) {
LOGW(
"OEMCrypto_DecryptCENC is warning of mixed HDCP output protection: "
"oec_session_id = %u",
oec_session_id_);
} else {
LOGE("OEMCrypto_DecryptCENC failed: status = %d", static_cast<int>(sts));
}
}
switch (sts) {
case OEMCrypto_SUCCESS:
case OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION:
return NO_ERROR;
case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES:
return INSUFFICIENT_CRYPTO_RESOURCES_5;
case OEMCrypto_ERROR_KEY_EXPIRED:
return NEED_KEY;
case OEMCrypto_ERROR_INVALID_SESSION:
return INVALID_SESSION_2;
case OEMCrypto_ERROR_DECRYPT_FAILED:
case OEMCrypto_ERROR_UNKNOWN_FAILURE:
return DECRYPT_ERROR;
case OEMCrypto_ERROR_INSUFFICIENT_HDCP:
return INSUFFICIENT_OUTPUT_PROTECTION;
case OEMCrypto_ERROR_ANALOG_OUTPUT:
return ANALOG_OUTPUT_ERROR;
case OEMCrypto_ERROR_OUTPUT_TOO_LARGE:
return OUTPUT_TOO_LARGE_ERROR;
case OEMCrypto_ERROR_SESSION_LOST_STATE:
return SESSION_LOST_STATE_ERROR;
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
return SYSTEM_INVALIDATED_ERROR;
default:
return UNKNOWN_ERROR;
}
}
bool CryptoSession::UsageInformationSupport(bool* has_support) {
LOGV("Checking if usage information is supported");
RETURN_IF_NOT_OPEN(false);
return UsageInformationSupport(requested_security_level_, has_support);
}
bool CryptoSession::UsageInformationSupport(SecurityLevel security_level,
bool* has_support) {
LOGV("Checking if usage information is supported: security_level = %d",
static_cast<int>(security_level));
RETURN_IF_UNINITIALIZED(false);
WithOecReadLock("UsageInformationSupport", [&] {
*has_support = OEMCrypto_SupportsUsageTable(security_level);
});
return true;
}
CdmResponseType CryptoSession::DeactivateUsageInformation(
const std::string& provider_session_token) {
LOGV("Deactivating usage information: id = %u", oec_session_id_);
uint8_t* pst = reinterpret_cast<uint8_t*>(
const_cast<char*>(provider_session_token.data()));
// TODO(fredgc or rfrias): make sure oec_session_id_ is valid.
OEMCryptoResult status;
WithOecWriteLock("DeactivateUsageInformation", [&] {
status = OEMCrypto_DeactivateUsageEntry(oec_session_id_, pst,
provider_session_token.length());
});
metrics_->oemcrypto_deactivate_usage_entry_.Increment(status);
if (status != OEMCrypto_SUCCESS) {
LOGE("OEMCrypto_DeactivateUsageEntry failed: status = %d",
static_cast<int>(status));
}
switch (status) {
case OEMCrypto_SUCCESS:
return NO_ERROR;
case OEMCrypto_ERROR_INVALID_CONTEXT:
return KEY_CANCELED;
case OEMCrypto_ERROR_SESSION_LOST_STATE:
return SESSION_LOST_STATE_ERROR;
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
return SYSTEM_INVALIDATED_ERROR;
default:
return DEACTIVATE_USAGE_ENTRY_ERROR;
}
}
CdmResponseType CryptoSession::GenerateUsageReport(
const std::string& provider_session_token, std::string* usage_report,
UsageDurationStatus* usage_duration_status, int64_t* seconds_since_started,
int64_t* seconds_since_last_played) {
LOGV("Generating usage report: id = %u", oec_session_id_);
RETURN_IF_NULL(usage_report, PARAMETER_NULL);
uint8_t* pst = reinterpret_cast<uint8_t*>(
const_cast<char*>(provider_session_token.data()));
size_t usage_length = 0;
OEMCryptoResult status;
WithOecWriteLock("GenerateUsageReport Attempt 1", [&] {
status = OEMCrypto_ReportUsage(oec_session_id_, pst,
provider_session_token.length(), nullptr,
&usage_length);
});
metrics_->oemcrypto_report_usage_.Increment(status);
if (status != OEMCrypto_SUCCESS && status != OEMCrypto_ERROR_SHORT_BUFFER) {
return MapOEMCryptoResult(status, GENERATE_USAGE_REPORT_ERROR,
"GenerateUsageReport");
}
std::vector<uint8_t> buffer(usage_length);
WithOecWriteLock("GenerateUsageReport Attempt 2", [&] {
status = OEMCrypto_ReportUsage(oec_session_id_, pst,
provider_session_token.length(), &buffer[0],
&usage_length);
});
metrics_->oemcrypto_report_usage_.Increment(status);
if (status != OEMCrypto_SUCCESS) {
return MapOEMCryptoResult(status, GENERATE_USAGE_REPORT_ERROR,
"OEMCrypto_ReportUsage");
}
if (usage_length != buffer.size()) {
buffer.resize(usage_length);
}
(*usage_report) =
std::string(reinterpret_cast<const char*>(&buffer[0]), buffer.size());
Unpacked_PST_Report pst_report(&buffer[0]);
*usage_duration_status = kUsageDurationsInvalid;
if (usage_length < pst_report.report_size()) {
LOGE(
"Parsed usage report smaller than expected: "
"usage_length = %zu, report_size = %zu",
usage_length, pst_report.report_size());
return NO_ERROR; // usage report available but no duration information
}
if (kUnused == pst_report.status()) {
*usage_duration_status = kUsageDurationPlaybackNotBegun;
return NO_ERROR;
}
LOGV("OEMCrypto_PST_Report.status: %d\n",
static_cast<int>(pst_report.status()));
LOGV("OEMCrypto_PST_Report.clock_security_level: %d\n",
static_cast<int>(pst_report.clock_security_level()));
LOGV("OEMCrypto_PST_Report.pst_length: %d\n",
static_cast<int>(pst_report.pst_length()));
LOGV("OEMCrypto_PST_Report.padding: %d\n",
static_cast<int>(pst_report.padding()));
LOGV("OEMCrypto_PST_Report.seconds_since_license_received: %ld\n",
pst_report.seconds_since_license_received());
LOGV("OEMCrypto_PST_Report.seconds_since_first_decrypt: %ld\n",
pst_report.seconds_since_first_decrypt());
LOGV("OEMCrypto_PST_Report.seconds_since_last_decrypt: %ld\n",
pst_report.seconds_since_last_decrypt());
LOGV("OEMCrypto_PST_Report: %s\n", b2a_hex(*usage_report).c_str());
if (kInactiveUnused == pst_report.status()) {
*usage_duration_status = kUsageDurationPlaybackNotBegun;
return NO_ERROR;
}
// Before OEMCrypto v13, When usage report state is inactive, we have to
// deduce whether the license was ever used.
if (kInactive == pst_report.status() &&
(0 > pst_report.seconds_since_first_decrypt() ||
pst_report.seconds_since_license_received() <
pst_report.seconds_since_first_decrypt())) {
*usage_duration_status = kUsageDurationPlaybackNotBegun;
return NO_ERROR;
}
*usage_duration_status = kUsageDurationsValid;
*seconds_since_started = pst_report.seconds_since_first_decrypt();
*seconds_since_last_played = pst_report.seconds_since_last_decrypt();
return NO_ERROR;
}
bool CryptoSession::IsAntiRollbackHwPresent() {
bool is_present;
WithOecReadLock("IsAntiRollbackHwPresent", [&] {
is_present = OEMCrypto_IsAntiRollbackHwPresent(requested_security_level_);
});
metrics_->oemcrypto_is_anti_rollback_hw_present_.Record(is_present);
return is_present;
}
CdmResponseType CryptoSession::GenerateNonce(uint32_t* nonce) {
RETURN_IF_NULL(nonce, PARAMETER_NULL);
OEMCryptoResult result;
WithOecWriteLock("GenerateNonce", [&] {
result = OEMCrypto_GenerateNonce(oec_session_id_, nonce);
});
metrics_->oemcrypto_generate_nonce_.Increment(result);
return MapOEMCryptoResult(result, NONCE_GENERATION_ERROR, "GenerateNonce");
}
bool CryptoSession::SetDestinationBufferType() {
if (Properties::oem_crypto_use_secure_buffers()) {
destination_buffer_type_ = OEMCrypto_BufferType_Secure;
} else if (Properties::oem_crypto_use_fifo()) {
destination_buffer_type_ = OEMCrypto_BufferType_Direct;
} else if (Properties::oem_crypto_use_userspace_buffers()) {
destination_buffer_type_ = OEMCrypto_BufferType_Clear;
} else {
return false;
}
is_destination_buffer_type_valid_ = true;
return true;
}
CdmResponseType CryptoSession::LoadProvisioning(
const std::string& signed_message, const std::string& core_message,
const std::string& signature, std::string* wrapped_private_key) {
LOGV("Loading provisioning certificate: id = %u", oec_session_id_);
if (wrapped_private_key == nullptr) {
LOGE("Missing wrapped |wrapped_private_key|");
return PARAMETER_NULL;
}
const std::string combined_message = core_message + signed_message;
// Round 1, get the size of the wrapped private key buffer.
size_t wrapped_private_key_length = 0;
OEMCryptoResult status;
WithOecSessionLock("LoadProvisioning Attempt 1", [&] {
M_TIME(status = OEMCrypto_LoadProvisioning(
oec_session_id_,
reinterpret_cast<const uint8_t*>(combined_message.data()),
combined_message.size(), core_message.size(),
reinterpret_cast<const uint8_t*>(signature.data()),
signature.size(), nullptr, &wrapped_private_key_length),
metrics_, oemcrypto_load_provisioning_, status);
});
if (status != OEMCrypto_ERROR_SHORT_BUFFER) {
return MapOEMCryptoResult(status, LOAD_PROVISIONING_ERROR,
"LoadProvisioning");
}
wrapped_private_key->resize(wrapped_private_key_length);
WithOecSessionLock("LoadProvisioning Attempt 2", [&] {
M_TIME(status = OEMCrypto_LoadProvisioning(
oec_session_id_,
reinterpret_cast<const uint8_t*>(combined_message.data()),
combined_message.size(), core_message.size(),
reinterpret_cast<const uint8_t*>(signature.data()),
signature.size(),
reinterpret_cast<uint8_t*>(&wrapped_private_key->front()),
&wrapped_private_key_length),
metrics_, oemcrypto_load_provisioning_, status);
});
return MapOEMCryptoResult(status, LOAD_PROVISIONING_ERROR,
"LoadProvisioning");
}
CdmResponseType CryptoSession::GetHdcpCapabilities(HdcpCapability* current,
HdcpCapability* max) {
LOGV("Getting HDCP capabilities: id = %u", oec_session_id_);
RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN);
return GetHdcpCapabilities(requested_security_level_, current, max);
}
CdmResponseType CryptoSession::GetHdcpCapabilities(SecurityLevel security_level,
HdcpCapability* current,
HdcpCapability* max) {
LOGV("Getting HDCP capabilities: id = %u, security_level = %d",
oec_session_id_, static_cast<int>(security_level));
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
RETURN_IF_NULL(current, PARAMETER_NULL);
RETURN_IF_NULL(max, PARAMETER_NULL);
OEMCryptoResult status;
WithOecReadLock("GetHdcpCapabilities", [&] {
status = OEMCrypto_GetHDCPCapability(security_level, current, max);
});
if (OEMCrypto_SUCCESS == status) {
metrics_->oemcrypto_current_hdcp_capability_.Record(*current);
metrics_->oemcrypto_max_hdcp_capability_.Record(*max);
} else {
metrics_->oemcrypto_current_hdcp_capability_.SetError(status);
metrics_->oemcrypto_max_hdcp_capability_.SetError(status);
}
return MapOEMCryptoResult(status, GET_HDCP_CAPABILITY_FAILED,
"GetHDCPCapability");
}
bool CryptoSession::GetSupportedCertificateTypes(
SupportedCertificateTypes* support) {
LOGV("Getting supported certificate types: id = %u", oec_session_id_);
RETURN_IF_UNINITIALIZED(false);
RETURN_IF_NULL(support, false);
uint32_t oec_support;
WithOecReadLock("GetSupportedCertificateTypes", [&] {
oec_support = OEMCrypto_SupportedCertificates(requested_security_level_);
});
support->rsa_2048_bit = oec_support & OEMCrypto_Supports_RSA_2048bit;
support->rsa_3072_bit = oec_support & OEMCrypto_Supports_RSA_3072bit;
support->rsa_cast = oec_support & OEMCrypto_Supports_RSA_CAST;
return true;
}
CdmResponseType CryptoSession::GetRandom(size_t data_length,
uint8_t* random_data) {
RETURN_IF_NULL(random_data, PARAMETER_NULL);
OEMCryptoResult sts;
WithOecReadLock("GetRandom",
[&] { sts = OEMCrypto_GetRandom(random_data, data_length); });
metrics_->oemcrypto_get_random_.Increment(sts);
return MapOEMCryptoResult(sts, RANDOM_GENERATION_ERROR, "GetRandom");
}
CdmResponseType CryptoSession::GetNumberOfOpenSessions(
SecurityLevel security_level, size_t* count) {
LOGV("Getting number of open sessions: id = %u, security_level = %d",
oec_session_id_, static_cast<int>(security_level));
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
RETURN_IF_NULL(count, PARAMETER_NULL);
size_t sessions_count;
OEMCryptoResult status;
WithOecReadLock("GetNumberOfOpenSessions", [&] {
status = OEMCrypto_GetNumberOfOpenSessions(security_level, &sessions_count);
});
if (OEMCrypto_SUCCESS == status) {
metrics_->oemcrypto_number_of_open_sessions_.Record(sessions_count);
*count = sessions_count;
} else {
metrics_->oemcrypto_number_of_open_sessions_.SetError(status);
}
return MapOEMCryptoResult(status, GET_NUMBER_OF_OPEN_SESSIONS_ERROR,
"GetNumberOfOpenSessions");
}
CdmResponseType CryptoSession::GetMaxNumberOfSessions(
SecurityLevel security_level, size_t* max) {
LOGV("Getting max number of sessions: id = %u, security_level = %d",
oec_session_id_, static_cast<int>(security_level));
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
RETURN_IF_NULL(max, PARAMETER_NULL);
size_t max_sessions = 0;
OEMCryptoResult status;
WithOecReadLock("GetMaxNumberOfSessions", [&] {
status = OEMCrypto_GetMaxNumberOfSessions(security_level, &max_sessions);
});
if (OEMCrypto_SUCCESS == status) {
metrics_->oemcrypto_max_number_of_sessions_.Record(max_sessions);
*max = max_sessions;
} else {
metrics_->oemcrypto_max_number_of_sessions_.SetError(status);
}
return MapOEMCryptoResult(status, GET_MAX_NUMBER_OF_OPEN_SESSIONS_ERROR,
"GetMaxNumberOfOpenSessions");
}
CdmResponseType CryptoSession::GetSrmVersion(uint16_t* srm_version) {
LOGV("Getting SRM version");
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
RETURN_IF_NULL(srm_version, PARAMETER_NULL);
OEMCryptoResult status;
WithOecReadLock("GetSrmVersion", [&] {
status = OEMCrypto_GetCurrentSRMVersion(srm_version);
});
// SRM is an optional feature. Whether it is implemented is up to the
// discretion of OEMs
if (status == OEMCrypto_ERROR_NOT_IMPLEMENTED) {
LOGV("OEMCrypto_GetCurrentSRMVersion not implemented");
return NOT_IMPLEMENTED_ERROR;
}
return MapOEMCryptoResult(status, GET_SRM_VERSION_ERROR,
"GetCurrentSRMVersion");
}
bool CryptoSession::IsSrmUpdateSupported() {
LOGV("Checking if SRM update is supported");
if (!IsInitialized()) return false;
return WithOecReadLock("IsSrmUpdateSupported",
[&] { return OEMCrypto_IsSRMUpdateSupported(); });
}
CdmResponseType CryptoSession::LoadSrm(const std::string& srm) {
LOGV("Loading SRM");
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
if (srm.empty()) {
LOGE("SRM is empty");
return INVALID_SRM_LIST;
}
OEMCryptoResult status;
WithOecWriteLock("LoadSrm", [&] {
status = OEMCrypto_LoadSRM(reinterpret_cast<const uint8_t*>(srm.data()),
srm.size());
});
return MapOEMCryptoResult(status, LOAD_SRM_ERROR, "LoadSRM");
}
bool CryptoSession::GetResourceRatingTier(uint32_t* tier) {
LOGV("Getting resource rating tier");
RETURN_IF_NOT_OPEN(false);
return GetResourceRatingTier(requested_security_level_, tier);
}
bool CryptoSession::GetResourceRatingTier(SecurityLevel security_level,
uint32_t* tier) {
LOGV("Getting resource rating tier: security_level = %d",
static_cast<int>(security_level));
RETURN_IF_UNINITIALIZED(false);
RETURN_IF_NULL(tier, false);
WithOecReadLock("GetResourceRatingTier", [&] {
*tier = OEMCrypto_ResourceRatingTier(security_level);
metrics_->oemcrypto_resource_rating_tier_.Record(*tier);
});
if (*tier < RESOURCE_RATING_TIER_MIN || *tier > RESOURCE_RATING_TIER_MAX) {
uint32_t api_version;
if (GetApiVersion(security_level, &api_version)) {
if (api_version >= OEM_CRYPTO_API_VERSION_SUPPORTS_RESOURCE_RATING_TIER) {
LOGW("Invalid resource rating tier: %u", *tier);
}
}
}
return true;
}
bool CryptoSession::GetBuildInformation(std::string* info) {
LOGV("Getting build information");
RETURN_IF_NOT_OPEN(false);
return GetBuildInformation(requested_security_level_, info);
}
bool CryptoSession::GetBuildInformation(SecurityLevel security_level,
std::string* info) {
LOGV("Getting build information: security_level = %d",
static_cast<int>(security_level));
RETURN_IF_UNINITIALIZED(false);
RETURN_IF_NULL(info, false);
const char* build_information;
WithOecReadLock("GetBuildInformation", [&] {
build_information = OEMCrypto_BuildInformation(security_level);
});
if (build_information == nullptr) {
LOGE("OEMCrypto_BuildInformation failed: Returned null");
return false;
}
info->assign(build_information);
return true;
}
bool CryptoSession::GetMaximumUsageTableEntries(SecurityLevel security_level,
size_t* number_of_entries) {
LOGV("Getting maximum usage table entries: security_level = %d",
static_cast<int>(security_level));
RETURN_IF_UNINITIALIZED(false);
if (number_of_entries == nullptr) {
LOGE("Output parameter |number_of_entries| not provided");
return false;
}
WithOecReadLock("GetMaxUsageTableEntries", [&] {
*number_of_entries = OEMCrypto_MaximumUsageTableHeaderSize(security_level);
});
// Record the number of entries into the metrics.
metrics_->oemcrypto_maximum_usage_table_header_size_.Record(
*number_of_entries);
return *number_of_entries >= kMinimumUsageTableEntriesSupported;
}
bool CryptoSession::GetDecryptHashSupport(SecurityLevel security_level,
uint32_t* decrypt_hash_support) {
LOGV("Checking if decrypt hash is supported");
RETURN_IF_UNINITIALIZED(false);
RETURN_IF_NULL(decrypt_hash_support, false);
WithOecReadLock("GetDecryptHashSupport", [&] {
*decrypt_hash_support = OEMCrypto_SupportsDecryptHash(security_level);
});
switch (*decrypt_hash_support) {
case OEMCrypto_Hash_Not_Supported:
case OEMCrypto_CRC_Clear_Buffer:
case OEMCrypto_Partner_Defined_Hash:
break;
default:
// Not flagging an error since it is only used in test
LOGW("OEMCrypto_SupportsDecryptHash unrecognized result = %d",
static_cast<int>(*decrypt_hash_support));
return false;
}
return true;
}
CdmResponseType CryptoSession::SetDecryptHash(uint32_t frame_number,
const std::string& hash) {
LOGV("Setting decrypt hash");
OEMCryptoResult sts;
WithOecSessionLock("SetDecryptHash", [&] {
sts = OEMCrypto_SetDecryptHash(
oec_session_id_, frame_number,
reinterpret_cast<const uint8_t*>(hash.data()), hash.size());
metrics_->oemcrypto_set_decrypt_hash_.Increment(sts);
});
return MapOEMCryptoResult(sts, SET_DECRYPT_HASH_ERROR, "SetDecryptHash");
}
CdmResponseType CryptoSession::GetDecryptHashError(std::string* error_string) {
LOGV("Getting decrypt hash error");
RETURN_IF_NULL(error_string, PARAMETER_NULL);
error_string->clear();
uint32_t failed_frame_number = 0;
OEMCryptoResult sts;
WithOecSessionLock("GetDecryptHashError", [&] {
sts = OEMCrypto_GetHashErrorCode(oec_session_id_, &failed_frame_number);
});
error_string->assign(std::to_string(sts));
if (sts != OEMCrypto_SUCCESS) {
LOGE("OEMCrypto_GetHashErrorCode failed: status = %d",
static_cast<int>(sts));
}
switch (sts) {
case OEMCrypto_SUCCESS:
case OEMCrypto_ERROR_BAD_HASH:
error_string->assign(std::to_string(sts));
error_string->append(",");
error_string->append(std::to_string(failed_frame_number));
return NO_ERROR;
case OEMCrypto_ERROR_SESSION_LOST_STATE:
return SESSION_LOST_STATE_ERROR;
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
return SYSTEM_INVALIDATED_ERROR;
case OEMCrypto_ERROR_UNKNOWN_FAILURE:
case OEMCrypto_ERROR_NOT_IMPLEMENTED:
default:
return GET_DECRYPT_HASH_ERROR;
}
}
CdmResponseType CryptoSession::GenericEncrypt(const std::string& in_buffer,
const std::string& key_id,
const std::string& iv,
CdmEncryptionAlgorithm algorithm,
std::string* out_buffer) {
LOGV("Generic encrypt: id = %u", oec_session_id_);
RETURN_IF_NULL(out_buffer, PARAMETER_NULL);
OEMCrypto_Algorithm oec_algorithm = GenericEncryptionAlgorithm(algorithm);
if (iv.size() != GenericEncryptionBlockSize(algorithm) ||
oec_algorithm == kInvalidAlgorithm) {
return INVALID_PARAMETERS_ENG_13;
}
if (out_buffer->size() < in_buffer.size()) {
out_buffer->resize(in_buffer.size());
}
// TODO(jfore): We need to select a key with a cipher mode and algorithm
// doesn't seem to fit. Is it ok to just use a default value here?
// Or do we need to pass it in?
CdmResponseType result = SelectKey(key_id, kCipherModeCbc);
if (result != NO_ERROR) return result;
OEMCryptoResult sts;
WithOecSessionLock("GenericEncrypt", [&] {
M_TIME(
sts = OEMCrypto_Generic_Encrypt(
oec_session_id_, reinterpret_cast<const uint8_t*>(in_buffer.data()),
in_buffer.size(), reinterpret_cast<const uint8_t*>(iv.data()),
oec_algorithm,
reinterpret_cast<uint8_t*>(const_cast<char*>(out_buffer->data()))),
metrics_, oemcrypto_generic_encrypt_, sts,
metrics::Pow2Bucket(in_buffer.size()));
});
if (OEMCrypto_SUCCESS != sts) {
LOGE("OEMCrypto_Generic_Encrypt failed: status = %d",
static_cast<int>(sts));
}
switch (sts) {
case OEMCrypto_SUCCESS:
return NO_ERROR;
case OEMCrypto_ERROR_KEY_EXPIRED:
return NEED_KEY;
case OEMCrypto_ERROR_NO_CONTENT_KEY:
case OEMCrypto_KEY_NOT_LOADED: // obsolete in v15.
return KEY_NOT_FOUND_3;
case OEMCrypto_ERROR_OUTPUT_TOO_LARGE:
return OUTPUT_TOO_LARGE_ERROR;
case OEMCrypto_ERROR_SESSION_LOST_STATE:
return SESSION_LOST_STATE_ERROR;
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
return SYSTEM_INVALIDATED_ERROR;
default:
return UNKNOWN_ERROR;
}
}
CdmResponseType CryptoSession::GenericDecrypt(const std::string& in_buffer,
const std::string& key_id,
const std::string& iv,
CdmEncryptionAlgorithm algorithm,
std::string* out_buffer) {
LOGV("Generic decrypt: id = %u", oec_session_id_);
RETURN_IF_NULL(out_buffer, PARAMETER_NULL);
OEMCrypto_Algorithm oec_algorithm = GenericEncryptionAlgorithm(algorithm);
if (iv.size() != GenericEncryptionBlockSize(algorithm) ||
oec_algorithm == kInvalidAlgorithm) {
return INVALID_PARAMETERS_ENG_14;
}
if (out_buffer->size() < in_buffer.size()) {
out_buffer->resize(in_buffer.size());
}
// TODO(jfore): We need to select a key with a cipher mode and algorithm
// doesn't seem to fit. Is it ok to just use a default value here?
// Or do we need to pass it in?
CdmResponseType result = SelectKey(key_id, kCipherModeCbc);
if (result != NO_ERROR) return result;
OEMCryptoResult sts;
WithOecSessionLock("GenericDecrypt", [&] {
M_TIME(
sts = OEMCrypto_Generic_Decrypt(
oec_session_id_, reinterpret_cast<const uint8_t*>(in_buffer.data()),
in_buffer.size(), reinterpret_cast<const uint8_t*>(iv.data()),
oec_algorithm,
reinterpret_cast<uint8_t*>(const_cast<char*>(out_buffer->data()))),
metrics_, oemcrypto_generic_decrypt_, sts,
metrics::Pow2Bucket(in_buffer.size()));
});
if (OEMCrypto_SUCCESS != sts) {
LOGE("OEMCrypto_Generic_Decrypt failed: status = %d",
static_cast<int>(sts));
}
switch (sts) {
case OEMCrypto_SUCCESS:
return NO_ERROR;
case OEMCrypto_ERROR_KEY_EXPIRED:
return NEED_KEY;
case OEMCrypto_ERROR_NO_CONTENT_KEY:
case OEMCrypto_KEY_NOT_LOADED: // obsolete in v15.
return KEY_NOT_FOUND_4;
case OEMCrypto_ERROR_OUTPUT_TOO_LARGE:
return OUTPUT_TOO_LARGE_ERROR;
case OEMCrypto_ERROR_SESSION_LOST_STATE:
return SESSION_LOST_STATE_ERROR;
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
return SYSTEM_INVALIDATED_ERROR;
default:
return UNKNOWN_ERROR;
}
}
CdmResponseType CryptoSession::GenericSign(const std::string& message,
const std::string& key_id,
CdmSigningAlgorithm algorithm,
std::string* signature) {
LOGV("Generic sign: id = %u", oec_session_id_);
RETURN_IF_NULL(signature, PARAMETER_NULL);
OEMCrypto_Algorithm oec_algorithm = GenericSigningAlgorithm(algorithm);
if (oec_algorithm == kInvalidAlgorithm) {
return INVALID_PARAMETERS_ENG_15;
}
OEMCryptoResult sts;
size_t length = signature->size();
// TODO(jfore): We need to select a key with a cipher mode and algorithm
// doesn't seem to fit. Is it ok to just use a default value here?
// Or do we need to pass it in?
CdmResponseType result = SelectKey(key_id, kCipherModeCbc);
if (result != NO_ERROR) return result;
// At most two attempts.
// The first attempt may fail due to buffer too short
for (int i = 0; i < 2; ++i) {
WithOecSessionLock("GenericSign", [&] {
M_TIME(
sts = OEMCrypto_Generic_Sign(
oec_session_id_, reinterpret_cast<const uint8_t*>(message.data()),
message.size(), oec_algorithm,
reinterpret_cast<uint8_t*>(const_cast<char*>(signature->data())),
&length),
metrics_, oemcrypto_generic_sign_, sts,
metrics::Pow2Bucket(message.size()));
});
if (OEMCrypto_SUCCESS == sts) {
// Trim signature buffer and done
signature->resize(length);
return NO_ERROR;
}
if (OEMCrypto_ERROR_SHORT_BUFFER != sts) {
break;
}
// Retry with proper-sized return buffer
signature->resize(length);
}
LOGE("OEMCrypto_Generic_Sign failed: status = %d", static_cast<int>(sts));
switch (sts) {
case OEMCrypto_ERROR_KEY_EXPIRED:
return NEED_KEY;
case OEMCrypto_ERROR_NO_CONTENT_KEY:
case OEMCrypto_KEY_NOT_LOADED: // obsolete in v15.
return KEY_NOT_FOUND_5;
case OEMCrypto_ERROR_OUTPUT_TOO_LARGE:
return OUTPUT_TOO_LARGE_ERROR;
case OEMCrypto_ERROR_SESSION_LOST_STATE:
return SESSION_LOST_STATE_ERROR;
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
return SYSTEM_INVALIDATED_ERROR;
default:
return UNKNOWN_ERROR;
}
}
CdmResponseType CryptoSession::GenericVerify(const std::string& message,
const std::string& key_id,
CdmSigningAlgorithm algorithm,
const std::string& signature) {
LOGV("Generic verify: id = %u", oec_session_id_);
OEMCrypto_Algorithm oec_algorithm = GenericSigningAlgorithm(algorithm);
if (oec_algorithm == kInvalidAlgorithm) {
return INVALID_PARAMETERS_ENG_16;
}
// TODO(jfore): We need to select a key with a cipher mode and algorithm
// doesn't seem to fit. Is it ok to just use a default value here?
// Or do we need to pass it in?
CdmResponseType result = SelectKey(key_id, kCipherModeCbc);
if (result != NO_ERROR) return result;
OEMCryptoResult sts;
WithOecSessionLock("GenericVerify", [&] {
M_TIME(
sts = OEMCrypto_Generic_Verify(
oec_session_id_, reinterpret_cast<const uint8_t*>(message.data()),
message.size(), oec_algorithm,
reinterpret_cast<const uint8_t*>(signature.data()),
signature.size()),
metrics_, oemcrypto_generic_verify_, sts,
metrics::Pow2Bucket(signature.size()));
});
if (OEMCrypto_SUCCESS != sts) {
LOGE("OEMCrypto_Generic_Verify failed: status = %d", static_cast<int>(sts));
}
switch (sts) {
case OEMCrypto_SUCCESS:
return NO_ERROR;
case OEMCrypto_ERROR_KEY_EXPIRED:
return NEED_KEY;
case OEMCrypto_ERROR_NO_CONTENT_KEY:
case OEMCrypto_KEY_NOT_LOADED: // obsolete in v15.
return KEY_NOT_FOUND_6;
case OEMCrypto_ERROR_OUTPUT_TOO_LARGE:
return OUTPUT_TOO_LARGE_ERROR;
case OEMCrypto_ERROR_SESSION_LOST_STATE:
return SESSION_LOST_STATE_ERROR;
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
return SYSTEM_INVALIDATED_ERROR;
default:
return UNKNOWN_ERROR;
}
}
CdmResponseType CryptoSession::GetUsageSupportType(
CdmUsageSupportType* usage_support_type) {
LOGV("Getting usage support type: id = %u", oec_session_id_);
RETURN_IF_NULL(usage_support_type, PARAMETER_NULL);
if (usage_support_type_ != kUnknownUsageSupport) {
*usage_support_type = usage_support_type_;
return NO_ERROR;
}
bool has_support = false;
if (!UsageInformationSupport(&has_support)) {
LOGE("UsageInformationSupport failed");
return USAGE_INFORMATION_SUPPORT_FAILED;
}
if (!has_support) {
*usage_support_type = usage_support_type_ = kNonSecureUsageSupport;
} else {
// As of v16, all supported version of OEMCrypto provide usage entry
// support or no usage info support.
*usage_support_type = usage_support_type_ = kUsageEntrySupport;
}
return NO_ERROR;
}
CdmResponseType CryptoSession::CreateUsageTableHeader(
SecurityLevel requested_security_level,
CdmUsageTableHeader* usage_table_header) {
LOGV("Creating usage table header: requested_security_level = %s",
requested_security_level == kLevel3
? QUERY_VALUE_SECURITY_LEVEL_L3.c_str()
: QUERY_VALUE_SECURITY_LEVEL_DEFAULT.c_str());
RETURN_IF_NULL(usage_table_header, PARAMETER_NULL);
usage_table_header->resize(kEstimatedInitialUsageTableHeader);
size_t usage_table_header_size = usage_table_header->size();
OEMCryptoResult result;
WithOecWriteLock("CreateUsageTableHeader Attempt 1", [&] {
result = OEMCrypto_CreateUsageTableHeader(
requested_security_level,
reinterpret_cast<uint8_t*>(
const_cast<char*>(usage_table_header->data())),
&usage_table_header_size);
metrics_->oemcrypto_create_usage_table_header_.Increment(result);
});
if (result == OEMCrypto_ERROR_SHORT_BUFFER) {
usage_table_header->resize(usage_table_header_size);
WithOecWriteLock("CreateUsageTableHeader Attempt 2", [&] {
result = OEMCrypto_CreateUsageTableHeader(
requested_security_level,
reinterpret_cast<uint8_t*>(
const_cast<char*>(usage_table_header->data())),
&usage_table_header_size);
metrics_->oemcrypto_create_usage_table_header_.Increment(result);
});
}
switch (result) {
case OEMCrypto_SUCCESS:
usage_table_header->resize(usage_table_header_size);
return NO_ERROR;
default:
return MapOEMCryptoResult(result, CREATE_USAGE_TABLE_ERROR,
"CreateUsageTableHeader");
}
}
CdmResponseType CryptoSession::LoadUsageTableHeader(
SecurityLevel requested_security_level,
const CdmUsageTableHeader& usage_table_header) {
LOGV("Loading usage table header: requested_security_level = %s",
requested_security_level == kLevel3
? QUERY_VALUE_SECURITY_LEVEL_L3.c_str()
: QUERY_VALUE_SECURITY_LEVEL_DEFAULT.c_str());
OEMCryptoResult result;
WithOecWriteLock("LoadUsageTableHeader", [&] {
result = OEMCrypto_LoadUsageTableHeader(
requested_security_level,
reinterpret_cast<const uint8_t*>(usage_table_header.data()),
usage_table_header.size());
metrics_->oemcrypto_load_usage_table_header_.Increment(result);
});
if (result != OEMCrypto_SUCCESS) {
if (result == OEMCrypto_WARNING_GENERATION_SKEW) {
LOGW("OEMCrypto_LoadUsageTableHeader warning: generation skew");
} else {
LOGE("OEMCrypto_LoadUsageTableHeader failed: status = %d",
static_cast<int>(result));
}
}
switch (result) {
case OEMCrypto_SUCCESS:
case OEMCrypto_WARNING_GENERATION_SKEW:
return NO_ERROR;
case OEMCrypto_ERROR_GENERATION_SKEW:
return LOAD_USAGE_HEADER_GENERATION_SKEW;
case OEMCrypto_ERROR_SIGNATURE_FAILURE:
return LOAD_USAGE_HEADER_SIGNATURE_FAILURE;
case OEMCrypto_ERROR_BAD_MAGIC:
return LOAD_USAGE_HEADER_BAD_MAGIC;
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
return SYSTEM_INVALIDATED_ERROR;
case OEMCrypto_ERROR_UNKNOWN_FAILURE:
default:
return LOAD_USAGE_HEADER_UNKNOWN_ERROR;
}
}
CdmResponseType CryptoSession::ShrinkUsageTableHeader(
SecurityLevel requested_security_level, uint32_t new_entry_count,
CdmUsageTableHeader* usage_table_header) {
LOGV("Shrinking usage table header: requested_security_level = %s",
requested_security_level == kLevel3
? QUERY_VALUE_SECURITY_LEVEL_L3.c_str()
: QUERY_VALUE_SECURITY_LEVEL_DEFAULT.c_str());
RETURN_IF_NULL(usage_table_header, PARAMETER_NULL);
size_t usage_table_header_len = 0;
OEMCryptoResult result;
WithOecWriteLock("ShrinkUsageTableHeader Attempt 1", [&] {
result = OEMCrypto_ShrinkUsageTableHeader(requested_security_level,
new_entry_count, nullptr,
&usage_table_header_len);
metrics_->oemcrypto_shrink_usage_table_header_.Increment(result);
});
if (result == OEMCrypto_ERROR_SHORT_BUFFER) {
usage_table_header->resize(usage_table_header_len);
WithOecWriteLock("ShrinkUsageTableHeader Attempt 2", [&] {
result = OEMCrypto_ShrinkUsageTableHeader(
requested_security_level, new_entry_count,
reinterpret_cast<uint8_t*>(
const_cast<char*>(usage_table_header->data())),
&usage_table_header_len);
metrics_->oemcrypto_shrink_usage_table_header_.Increment(result);
});
}
switch (result) {
case OEMCrypto_SUCCESS:
usage_table_header->resize(usage_table_header_len);
return NO_ERROR;
case OEMCrypto_ERROR_ENTRY_IN_USE:
return SHRINK_USAGE_TABLE_HEADER_ENTRY_IN_USE;
default:
return MapOEMCryptoResult(result, SHRINK_USAGE_TABLE_HEADER_UNKNOWN_ERROR,
"ShrinkUsageTableHeader");
}
}
CdmResponseType CryptoSession::CreateUsageEntry(uint32_t* entry_number) {
LOGV("Creating usage entry: id = %u", oec_session_id_);
RETURN_IF_NULL(entry_number, PARAMETER_NULL);
OEMCryptoResult result;
WithOecWriteLock("CreateUsageEntry", [&] {
result = OEMCrypto_CreateNewUsageEntry(oec_session_id_, entry_number);
metrics_->oemcrypto_create_new_usage_entry_.Increment(result);
});
if (result != OEMCrypto_SUCCESS) {
LOGE("OEMCrypto_CreateNewUsageEntry failed: status = %d",
static_cast<int>(result));
}
switch (result) {
case OEMCrypto_SUCCESS:
return NO_ERROR;
case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES:
return INSUFFICIENT_CRYPTO_RESOURCES_3;
case OEMCrypto_ERROR_SESSION_LOST_STATE:
return SESSION_LOST_STATE_ERROR;
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
return SYSTEM_INVALIDATED_ERROR;
default:
return CREATE_USAGE_ENTRY_UNKNOWN_ERROR;
}
}
CdmResponseType CryptoSession::LoadUsageEntry(
uint32_t entry_number, const CdmUsageEntry& usage_entry) {
LOGV("Loading usage entry: id = %u", oec_session_id_);
OEMCryptoResult result;
WithOecWriteLock("LoadUsageEntry", [&] {
result = OEMCrypto_LoadUsageEntry(
oec_session_id_, entry_number,
reinterpret_cast<const uint8_t*>(usage_entry.data()),
usage_entry.size());
metrics_->oemcrypto_load_usage_entry_.Increment(result);
});
if (result != OEMCrypto_SUCCESS) {
if (result == OEMCrypto_WARNING_GENERATION_SKEW) {
LOGW("OEMCrypto_LoadUsageEntry warning: generation skew");
} else {
LOGE("OEMCrypto_LoadUsageEntry failed: status = %d",
static_cast<int>(result));
}
}
switch (result) {
case OEMCrypto_SUCCESS:
case OEMCrypto_WARNING_GENERATION_SKEW:
return NO_ERROR;
case OEMCrypto_ERROR_INVALID_SESSION:
// This case is special, as it could imply that the provided
// session ID is invalid (CDM internal bug), or that the entry
// being loaded is already in use in a different session.
// It is up to the caller to handle this.
return LOAD_USAGE_ENTRY_INVALID_SESSION;
case OEMCrypto_ERROR_GENERATION_SKEW:
return LOAD_USAGE_ENTRY_GENERATION_SKEW;
case OEMCrypto_ERROR_SIGNATURE_FAILURE:
return LOAD_USAGE_ENTRY_SIGNATURE_FAILURE;
default:
return MapOEMCryptoResult(result, LOAD_USAGE_ENTRY_UNKNOWN_ERROR,
"LoadUsageEntry");
}
}
CdmResponseType CryptoSession::UpdateUsageEntry(
CdmUsageTableHeader* usage_table_header, CdmUsageEntry* usage_entry) {
LOGV("Updating usage entry: id = %u", oec_session_id_);
RETURN_IF_NULL(usage_table_header, PARAMETER_NULL);
RETURN_IF_NULL(usage_entry, PARAMETER_NULL);
size_t usage_table_header_len = 0;
size_t usage_entry_len = 0;
OEMCryptoResult result;
WithOecWriteLock("UpdateUsageEntry Attempt 1", [&] {
result = OEMCrypto_UpdateUsageEntry(oec_session_id_, nullptr,
&usage_table_header_len, nullptr,
&usage_entry_len);
});
metrics_->oemcrypto_update_usage_entry_.Increment(result);
if (result == OEMCrypto_ERROR_SHORT_BUFFER) {
usage_table_header->resize(usage_table_header_len);
usage_entry->resize(usage_entry_len);
WithOecWriteLock("UpdateUsageEntry Attempt 2", [&] {
result = OEMCrypto_UpdateUsageEntry(
oec_session_id_,
reinterpret_cast<uint8_t*>(
const_cast<char*>(usage_table_header->data())),
&usage_table_header_len,
reinterpret_cast<uint8_t*>(const_cast<char*>(usage_entry->data())),
&usage_entry_len);
});
metrics_->oemcrypto_update_usage_entry_.Increment(result);
}
if (result == OEMCrypto_SUCCESS) {
usage_table_header->resize(usage_table_header_len);
usage_entry->resize(usage_entry_len);
}
return MapOEMCryptoResult(result, UPDATE_USAGE_ENTRY_UNKNOWN_ERROR,
"UpdateUsageEntry");
}
CdmResponseType CryptoSession::MoveUsageEntry(uint32_t new_entry_number) {
LOGV("Moving usage entry: id = %u", oec_session_id_);
OEMCryptoResult result;
WithOecWriteLock("MoveUsageEntry", [&] {
result = OEMCrypto_MoveEntry(oec_session_id_, new_entry_number);
metrics_->oemcrypto_move_entry_.Increment(result);
});
switch (result) {
case OEMCrypto_ERROR_ENTRY_IN_USE:
LOGW("OEMCrypto_MoveEntry failed: Destination index in use: index = %u",
new_entry_number);
return MOVE_USAGE_ENTRY_DESTINATION_IN_USE;
default:
return MapOEMCryptoResult(result, MOVE_USAGE_ENTRY_UNKNOWN_ERROR,
"MoveUsageEntry");
}
}
bool CryptoSession::GetAnalogOutputCapabilities(bool* can_support_output,
bool* can_disable_output,
bool* can_support_cgms_a) {
LOGV("Getting analog output capabilities: id = %u", oec_session_id_);
uint32_t flags;
WithOecReadLock("GetAnalogOutputCapabilities", [&] {
flags = OEMCrypto_GetAnalogOutputFlags(requested_security_level_);
});
if ((flags & OEMCrypto_Unknown_Analog_Output) != 0) return false;
*can_support_cgms_a = flags & OEMCrypto_Supports_CGMS_A;
*can_support_output = flags & OEMCrypto_Supports_Analog_Output;
*can_disable_output = flags & OEMCrypto_Can_Disable_Analog_Ouptput;
return true;
}
OEMCrypto_Algorithm CryptoSession::GenericSigningAlgorithm(
CdmSigningAlgorithm algorithm) {
if (kSigningAlgorithmHmacSha256 == algorithm) {
return OEMCrypto_HMAC_SHA256;
} else {
return kInvalidAlgorithm;
}
}
OEMCrypto_Algorithm CryptoSession::GenericEncryptionAlgorithm(
CdmEncryptionAlgorithm algorithm) {
if (kEncryptionAlgorithmAesCbc128 == algorithm) {
return OEMCrypto_AES_CBC_128_NO_PADDING;
} else {
return kInvalidAlgorithm;
}
}
size_t CryptoSession::GenericEncryptionBlockSize(
CdmEncryptionAlgorithm algorithm) {
if (kEncryptionAlgorithmAesCbc128 == algorithm) {
return kAes128BlockSize;
} else {
return 0;
}
}
// OEMCryptoResult OEMCrypto_DecryptCENC(
// OEMCrypto_SESSION session,
// const OEMCrypto_SampleDescription* samples, // an array of samples.
// size_t samples_length, // the number of samples.
// const OEMCrypto_CENCEncryptPatternDesc* pattern);
OEMCryptoResult CryptoSession::DecryptMultipleSamples(
const std::vector<OEMCrypto_SampleDescription>& samples,
CdmCipherMode cipher_mode,
const OEMCrypto_CENCEncryptPatternDesc& pattern) {
OEMCryptoResult sts = OEMCrypto_ERROR_BUFFER_TOO_LARGE;
// If there's only one sample, automatically fall through to avoid a redundant
// roundtrip through OEMCrypto_DecryptCENC()
if (samples.size() > 1) {
WithOecSessionLock("DecryptMultipleSamples", [&] {
sts = key_session_->Decrypt(samples.data(), samples.size(), pattern);
});
}
if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE) {
// Fall back to sending each sample individually
for (const OEMCrypto_SampleDescription& sample : samples) {
sts = DecryptSample(sample, cipher_mode, pattern);
if (sts != OEMCrypto_SUCCESS) break;
}
}
return sts;
}
OEMCryptoResult CryptoSession::DecryptSample(
const OEMCrypto_SampleDescription& sample, CdmCipherMode cipher_mode,
const OEMCrypto_CENCEncryptPatternDesc& pattern) {
OEMCryptoResult sts = OEMCrypto_ERROR_BUFFER_TOO_LARGE;
// If there's only one subsample and it contains only one type of region,
// automatically fall through to avoid a redundant roundtrip through
// OEMCrypto_DecryptCENC()
if (sample.subsamples_length > 1 ||
(sample.subsamples[0].num_bytes_clear > 0 &&
sample.subsamples[0].num_bytes_encrypted > 0)) {
WithOecSessionLock("DecryptSample", [&] {
sts = key_session_->Decrypt(&sample, 1, pattern);
});
}
if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE) {
// Fall back to sending each subsample region individually
sts = OEMCrypto_SUCCESS;
OEMCrypto_SampleDescription fake_sample = sample;
for (size_t i = 0; i < sample.subsamples_length; ++i) {
const OEMCrypto_SubSampleDescription& original_subsample =
sample.subsamples[i];
if (original_subsample.num_bytes_clear > 0) {
const size_t length = original_subsample.num_bytes_clear;
OEMCrypto_SubSampleDescription clear_subsample{
length,
0, // num_bytes_encrypted
0, // subsample_flags
0 // block_offset, not relevant for clear data
};
if (original_subsample.subsample_flags & OEMCrypto_FirstSubsample) {
clear_subsample.subsample_flags |= OEMCrypto_FirstSubsample;
}
if ((original_subsample.subsample_flags & OEMCrypto_LastSubsample) &&
original_subsample.num_bytes_encrypted == 0) {
clear_subsample.subsample_flags |= OEMCrypto_LastSubsample;
}
fake_sample.buffers.input_data_length = length;
fake_sample.subsamples = &clear_subsample;
fake_sample.subsamples_length = 1;
sts = LegacyDecrypt(fake_sample, cipher_mode, pattern);
if (sts != OEMCrypto_SUCCESS) break;
fake_sample.buffers.input_data += length;
AdvanceDestBuffer(&fake_sample.buffers.output_descriptor, length);
}
if (original_subsample.num_bytes_encrypted > 0) {
const size_t length = original_subsample.num_bytes_encrypted;
OEMCrypto_SubSampleDescription encrypted_subsample{
0, // num_bytes_clear
length,
0, // subsample_flags
original_subsample.block_offset};
if ((original_subsample.subsample_flags & OEMCrypto_FirstSubsample) &&
original_subsample.num_bytes_clear == 0) {
encrypted_subsample.subsample_flags |= OEMCrypto_FirstSubsample;
}
if (original_subsample.subsample_flags & OEMCrypto_LastSubsample) {
encrypted_subsample.subsample_flags |= OEMCrypto_LastSubsample;
}
fake_sample.buffers.input_data_length = length;
fake_sample.subsamples = &encrypted_subsample;
fake_sample.subsamples_length = 1;
sts = LegacyDecrypt(fake_sample, cipher_mode, pattern);
if (sts != OEMCrypto_SUCCESS) break;
fake_sample.buffers.input_data += length;
AdvanceDestBuffer(&fake_sample.buffers.output_descriptor, length);
if (cipher_mode == kCipherModeCtr) {
AdvanceIvCtr(&fake_sample.iv,
original_subsample.block_offset +
original_subsample.num_bytes_encrypted);
}
}
}
}
return sts;
}
OEMCryptoResult CryptoSession::LegacyDecrypt(
const OEMCrypto_SampleDescription& sample, CdmCipherMode cipher_mode,
const OEMCrypto_CENCEncryptPatternDesc& pattern) {
const size_t max_chunk_size = GetMaxSubsampleRegionSize();
OEMCryptoResult sts = OEMCrypto_ERROR_NOT_IMPLEMENTED;
// We can be sure this is only called with one subsample containing one
// region of data.
const OEMCrypto_SubSampleDescription& subsample = sample.subsamples[0];
const bool is_encrypted = (subsample.num_bytes_encrypted > 0);
const bool is_only_subsample =
subsample.subsample_flags ==
(OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample);
if (!is_encrypted && is_only_subsample) {
WithOecSessionLock("LegacyDecrypt() calling CopyBuffer", [&] {
M_TIME(sts = OEMCrypto_CopyBuffer(
oec_session_id_, sample.buffers.input_data,
sample.buffers.input_data_length,
&sample.buffers.output_descriptor, subsample.subsample_flags),
metrics_, oemcrypto_copy_buffer_, sts,
metrics::Pow2Bucket(sample.buffers.input_data_length));
});
if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE &&
sample.buffers.input_data_length > max_chunk_size) {
// OEMCrypto_CopyBuffer rejected the buffer as too large, so chunk it up
// into 100 KiB sections.
sts = LegacyCopyBufferInChunks(sample, max_chunk_size);
}
}
if (is_encrypted || sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) {
WithOecSessionLock("LegacyDecrypt() calling key_session_->Decrypt()", [&] {
sts = key_session_->Decrypt(&sample, 1, pattern);
});
if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE) {
// OEMCrypto_DecryptCENC rejected the buffer as too large, so chunk it
// up into sections no more than 100 KiB. The exact chunk size needs to
// be an even number of pattern repetitions long or else the pattern
// will get out of sync.
const size_t pattern_length =
(pattern.encrypt + pattern.skip) * kAes128BlockSize;
const size_t chunk_size =
pattern_length > 0
? max_chunk_size - (max_chunk_size % pattern_length)
: max_chunk_size;
if (sample.buffers.input_data_length > chunk_size) {
sts = LegacyDecryptInChunks(sample, cipher_mode, pattern, chunk_size);
}
}
}
return sts;
}
OEMCryptoResult CryptoSession::LegacyCopyBufferInChunks(
const OEMCrypto_SampleDescription& sample, size_t max_chunk_size) {
const uint8_t* input_data = sample.buffers.input_data;
size_t remaining_input_data = sample.buffers.input_data_length;
OEMCrypto_DestBufferDesc output_descriptor = sample.buffers.output_descriptor;
uint8_t subsample_flags = OEMCrypto_FirstSubsample;
OEMCryptoResult sts = OEMCrypto_SUCCESS;
while (remaining_input_data > 0) {
// Calculate the size of the next chunk.
const size_t chunk_size = std::min(remaining_input_data, max_chunk_size);
// Re-add "last subsample" flag if this is the last subsample.
if (chunk_size == remaining_input_data) {
subsample_flags |= OEMCrypto_LastSubsample;
}
WithOecSessionLock("LegacyCopyBufferInChunks", [&] {
M_TIME(sts = OEMCrypto_CopyBuffer(oec_session_id_, input_data, chunk_size,
&output_descriptor, subsample_flags),
metrics_, oemcrypto_copy_buffer_, sts,
metrics::Pow2Bucket(chunk_size));
});
if (sts != OEMCrypto_SUCCESS) break;
// Clear any subsample flags before the next loop iteration.
subsample_flags = 0;
// Update the source and destination buffers based on the amount of data
// copied.
input_data += chunk_size;
remaining_input_data -= chunk_size;
AdvanceDestBuffer(&output_descriptor, chunk_size);
}
return sts;
}
OEMCryptoResult CryptoSession::LegacyDecryptInChunks(
const OEMCrypto_SampleDescription& sample, CdmCipherMode cipher_mode,
const OEMCrypto_CENCEncryptPatternDesc& pattern, size_t max_chunk_size) {
const OEMCrypto_SubSampleDescription& subsample = sample.subsamples[0];
const bool is_protected = (subsample.num_bytes_encrypted > 0);
OEMCrypto_SampleDescription fake_sample = sample;
OEMCrypto_SubSampleDescription fake_subsample = subsample;
fake_sample.subsamples = &fake_subsample;
fake_subsample.subsample_flags =
subsample.subsample_flags & OEMCrypto_FirstSubsample;
size_t remaining_input_data = sample.buffers.input_data_length;
OEMCryptoResult sts = OEMCrypto_SUCCESS;
while (remaining_input_data > 0) {
// Calculate the size of the next chunk.
const size_t chunk_size = std::min(remaining_input_data, max_chunk_size);
fake_sample.buffers.input_data_length = chunk_size;
if (is_protected) {
fake_subsample.num_bytes_encrypted = chunk_size;
} else {
fake_subsample.num_bytes_clear = chunk_size;
}
// Re-add "last subsample" flag if this is the last subsample.
if (chunk_size == remaining_input_data) {
fake_subsample.subsample_flags |=
subsample.subsample_flags & OEMCrypto_LastSubsample;
}
// |pattern| and |fake_subsample.block_offset| do not need to change because
// |max_chunk_size| is guaranteed to be an even multiple of the
// pattern length long, which is also guaranteed to be an exact number
// of AES blocks long.
WithOecSessionLock("LegacyDecryptInChunks", [&] {
sts = key_session_->Decrypt(&fake_sample, 1, pattern);
});
if (sts != OEMCrypto_SUCCESS) break;
// Clear any subsample flags before the next loop iteration.
fake_subsample.subsample_flags = 0;
// Update the IV so that it is valid for the next iteration. This should not
// be done on the last iteration both to save time and because the 'cbcs'
// calculation can underflow if the chunk is less than the max chunk size.
if (remaining_input_data > chunk_size) {
if (cipher_mode == kCipherModeCtr) {
// For 'cenc', update the IV depending on how many encrypted blocks
// we passed.
AdvanceIvCtr(&fake_sample.iv, chunk_size + fake_subsample.block_offset);
} else if (cipher_mode == kCipherModeCbc) {
// For 'cbcs', use the last ciphertext block as the next IV. The last
// block that was encrypted is probably not the last block of the
// subsample. Since the max buffer size is guaranteed to be an even
// number of pattern repetitions long, we can use the pattern to know
// how many blocks to look back.
const uint8_t* const buffer_end =
fake_sample.buffers.input_data + chunk_size;
const uint8_t* const block_end =
buffer_end - kAes128BlockSize * pattern.skip;
static_assert(sizeof(fake_sample.iv) == kAes128BlockSize,
"The size of an AES-128 block and the size of an AES-128 "
"IV have become misaligned.");
memcpy(fake_sample.iv, block_end - kAes128BlockSize, kAes128BlockSize);
}
}
// Update the source and destination buffers based on the amount of data
// copied.
fake_sample.buffers.input_data += chunk_size;
remaining_input_data -= chunk_size;
AdvanceDestBuffer(&fake_sample.buffers.output_descriptor, chunk_size);
}
return sts;
}
template <class Func>
auto CryptoSession::WithStaticFieldWriteLock(const char* tag, Func body)
-> decltype(body()) {
LOGV("Static field write lock: %s", tag);
std::unique_lock<shared_mutex> auto_lock(static_field_mutex_);
return body();
}
template <class Func>
auto CryptoSession::WithStaticFieldReadLock(const char* tag, Func body)
-> decltype(body()) {
LOGV("Static field read lock: %s", tag);
shared_lock<shared_mutex> auto_lock(static_field_mutex_);
return body();
}
template <class Func>
auto CryptoSession::WithOecWriteLock(const char* tag, Func body)
-> decltype(body()) {
LOGV("OEMCrypto write lock: %s", tag);
std::unique_lock<shared_mutex> auto_lock(oem_crypto_mutex_);
return body();
}
template <class Func>
auto CryptoSession::WithOecReadLock(const char* tag, Func body)
-> decltype(body()) {
LOGV("OEMCrypto read lock: %s", tag);
shared_lock<shared_mutex> auto_lock(oem_crypto_mutex_);
return body();
}
template <class Func>
auto CryptoSession::WithOecSessionLock(const char* tag, Func body)
-> decltype(body()) {
LOGV("OEMCrypto session lock: %s", tag);
shared_lock<shared_mutex> oec_auto_lock(oem_crypto_mutex_);
std::unique_lock<std::mutex> session_auto_lock(oem_crypto_session_mutex_);
return body();
}
bool CryptoSession::IsInitialized() {
return WithStaticFieldReadLock("IsInitialized", [] { return initialized_; });
}
// CryptoSesssionFactory support
std::mutex CryptoSession::factory_mutex_;
// The factory will either be set by WvCdmTestBase, or a default factory is
// created on the first call to MakeCryptoSession.
std::unique_ptr<CryptoSessionFactory> CryptoSession::factory_ =
std::unique_ptr<CryptoSessionFactory>();
CryptoSession* CryptoSession::MakeCryptoSession(
metrics::CryptoMetrics* crypto_metrics) {
std::unique_lock<std::mutex> auto_lock(factory_mutex_);
// If the factory_ has not been set, then use a default factory.
if (!factory_) factory_.reset(new CryptoSessionFactory());
return factory_->MakeCryptoSession(crypto_metrics);
}
CryptoSession* CryptoSessionFactory::MakeCryptoSession(
metrics::CryptoMetrics* crypto_metrics) {
return new CryptoSession(crypto_metrics);
}
} // namespace wvcdm