Files
android/libwvdrmengine/cdm/core/src/crypto_session.cpp
Kyle Zhang d73997bc0b Revert "Limit output buffer size during decrypt fallback"
Revert submission 28914157

Reason for revert: b/372348308

Reverted changes: /q/submissionid:28914157

Change-Id: Ib77156ffe6abed0f8feee5d9f60f24a90e749ff8
2024-10-14 22:28:42 +00:00

3613 lines
137 KiB
C++

// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine License
// Agreement.
//
// Crypto - wrapper classes for OEMCrypto interface
//
#include "crypto_session.h"
#include <inttypes.h>
#include <string.h>
#include <algorithm>
#include <iostream>
#include <memory>
#include "advance_iv_ctr.h"
#include "arraysize.h"
#include "cdm_random.h"
#include "cdm_usage_table.h"
#include "content_key_session.h"
#include "crypto_key.h"
#include "entitlement_key_session.h"
#include "log.h"
#include "odk_structs.h"
#include "okp_fallback_policy.h"
#include "platform.h"
#include "privacy_crypto.h"
#include "properties.h"
#include "pst_report.h"
#include "string_conversions.h"
#include "wv_cdm_constants.h"
// Stringify turns macro arguments into static C strings.
// Example: STRINGIFY(this_argument) -> "this_argument"
#define STRINGIFY(PARAM) #PARAM
namespace {
bool WrapIfNecessary(bool ret_value) { return ret_value; }
wvcdm::CdmResponseType WrapIfNecessary(wvcdm::CdmResponseEnum ret_value) {
return wvcdm::CdmResponseType(ret_value);
}
wvcdm::CdmSecurityLevel WrapIfNecessary(wvcdm::CdmSecurityLevel ret_value) {
return ret_value;
}
OEMCryptoResult WrapIfNecessary(OEMCryptoResult ret_value) { return ret_value; }
} // namespace
#define RETURN_IF_NULL(PARAM, ret_value) \
if ((PARAM) == nullptr) { \
LOGE("Output parameter |" STRINGIFY(PARAM) "| not provided"); \
return WrapIfNecessary(ret_value); \
}
#define RETURN_IF_UNINITIALIZED(ret_value) \
if (!IsInitialized()) { \
LOGE("Crypto session is not initialized"); \
return WrapIfNecessary(ret_value); \
}
#define RETURN_IF_NOT_OPEN(ret_value) \
if (!IsOpen()) { \
LOGE("Crypto session is not open"); \
return WrapIfNecessary(ret_value); \
}
#define CRYPTO_ERROR(cdm_err, oem_err) \
CdmResponseType(cdm_err, oem_err, __func__)
#ifdef HAS_DUAL_KEY
/**
* Internal only OEMCrypto method. This is called before parsing the license
* response to indicate the response uses dual keys. If this isn't called,
* it is using single keys.
*/
extern "C" OEMCryptoResult OEMCrypto_UseSecondaryKey(OEMCrypto_SESSION session,
bool dual_key);
#endif
namespace wvcdm {
namespace {
using UsageTableLock = std::unique_lock<std::recursive_mutex>;
constexpr size_t KiB = 1024;
constexpr size_t MiB = 1024 * 1024;
constexpr uint32_t kRsaSignatureLength = 256;
constexpr size_t kEstimatedInitialUsageTableHeader = 40;
constexpr size_t kAes128BlockSize = 16;
constexpr int kMaxTerminateCountDown = 5;
constexpr char kUnavailableHdcpLevel[] = "<unavailable>";
const std::string kStringNotAvailable = "NA";
// TODO(b/174412779): Remove when b/170704368 is fixed.
// This is a Qualcomm specific error code
const int kRsaSsaPssSignatureLengthError = 10085;
// 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(wvutil::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];
constexpr size_t kMaxExternalDeviceIdLength = 64;
// 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,
CdmResponseEnum 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));
}
CdmResponseEnum status{};
switch (result) {
case OEMCrypto_SUCCESS:
status = NO_ERROR;
break;
case OEMCrypto_ERROR_NOT_IMPLEMENTED:
status = NOT_IMPLEMENTED_ERROR;
break;
case OEMCrypto_ERROR_TOO_MANY_SESSIONS:
status = INSUFFICIENT_CRYPTO_RESOURCES;
break;
case OEMCrypto_ERROR_SESSION_LOST_STATE:
status = SESSION_LOST_STATE_ERROR;
break;
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
status = SYSTEM_INVALIDATED_ERROR;
break;
default:
status = default_status;
}
return CdmResponseType(status, result, crypto_session_method);
}
void AdvanceDestBuffer(OEMCrypto_DestBufferDesc* dest_buffer, size_t bytes) {
switch (dest_buffer->type) {
case OEMCrypto_BufferType_Clear:
dest_buffer->buffer.clear.clear_buffer += bytes;
dest_buffer->buffer.clear.clear_buffer_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",
static_cast<unsigned int>(dest_buffer->type));
}
bool GetGenericSigningAlgorithm(CdmSigningAlgorithm algorithm,
OEMCrypto_Algorithm* oec_algorithm) {
RETURN_IF_NULL(oec_algorithm, false);
if (kSigningAlgorithmHmacSha256 != algorithm) {
LOGW("Unrecognized signing algorithm: %d", algorithm);
return false;
}
*oec_algorithm = OEMCrypto_HMAC_SHA256;
return true;
}
bool GetGenericEncryptionAlgorithm(CdmEncryptionAlgorithm algorithm,
OEMCrypto_Algorithm* oec_algorithm) {
RETURN_IF_NULL(oec_algorithm, false);
if (kEncryptionAlgorithmAesCbc128 != algorithm) {
LOGW("Unrecognized encryption algorithm: %d", algorithm);
return false;
}
*oec_algorithm = OEMCrypto_AES_CBC_128_NO_PADDING;
return true;
}
size_t GenericEncryptionBlockSize(CdmEncryptionAlgorithm algorithm) {
if (kEncryptionAlgorithmAesCbc128 != algorithm) {
LOGW("Unrecognized encryption algorithm: %d", algorithm);
return 0;
}
return kAes128BlockSize;
}
uint8_t* MutableStringDataPointer(std::string* s) {
if (s == nullptr) return nullptr;
if (s->empty()) return nullptr;
return reinterpret_cast<uint8_t*>(&s->front());
}
} // namespace
// CryptoSession variables allocation.
wvutil::shared_mutex CryptoSession::static_field_mutex_;
wvutil::shared_mutex CryptoSession::oem_crypto_mutex_;
bool CryptoSession::initialized_ = false;
bool CryptoSession::needs_keybox_provisioning_ = false;
int CryptoSession::session_count_ = 0;
int CryptoSession::termination_counter_ = 0;
std::unique_ptr<CdmUsageTable> CryptoSession::usage_table_l1_;
std::unique_ptr<CdmUsageTable> CryptoSession::usage_table_l3_;
std::recursive_mutex CryptoSession::usage_table_mutex_;
std::atomic<uint64_t> CryptoSession::request_id_index_source_(0);
std::unique_ptr<okp::SystemFallbackPolicy>
CryptoSession::okp_fallback_policy_l1_;
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",
IdToString(field));
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",
IdToString(field));
substring.offset = 0;
substring.length = 0;
} else {
substring.offset = pos;
substring.length = field.length();
}
}
return substring;
}
OEMCryptoCipherMode ToOEMCryptoCipherMode(CdmCipherMode cipher_mode) {
return cipher_mode == kCipherModeCtr ? OEMCrypto_CipherMode_CENC
: OEMCrypto_CipherMode_CBCS;
}
// static
const char* CryptoSession::HdcpCapabilityToString(HdcpCapability hdcp_level) {
switch (hdcp_level) {
case HDCP_NONE:
return "None";
case HDCP_V1: // Means v1.x
return "v1.x";
case HDCP_V2: // Means v2.0
return "v2.0";
case HDCP_V2_1:
return "v2.1";
case HDCP_V2_2:
return "v2.2";
case HDCP_V2_3:
return "v2.3";
case HDCP_V1_0:
return "v1.0";
case HDCP_V1_1:
return "v1.1";
case HDCP_V1_2:
return "v1.2";
case HDCP_V1_3:
return "v1.3";
case HDCP_V1_4:
return "v1.4";
case HDCP_NO_DIGITAL_OUTPUT:
return "No-Digital-Output";
}
return UnknownEnumValueToString(static_cast<int>(hdcp_level));
}
CryptoSession::CryptoSession(metrics::CryptoMetrics* metrics)
: metrics_(metrics),
system_id_(NULL_SYSTEM_ID),
open_(false),
pre_provision_token_type_(kClientTokenUninitialized),
update_usage_table_after_close_session_(false),
is_destination_buffer_type_valid_(false),
requested_security_level_(kLevelDefault),
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(
RequestedSecurityLevel 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_BootCertificateChain:
type = kClientTokenBootCertChain;
break;
case OEMCrypto_DrmReprovisioning:
type = kClientTokenDrmCertificateReprovisioning;
break;
case OEMCrypto_ProvisioningError:
default:
if (static_cast<int>(method) == 0 && needs_keybox_provisioning_) {
LOGW("Overriding provisioning method, assuming keybox");
type = kClientTokenKeybox;
break;
}
LOGE("OEMCrypto_GetProvisioningMethod failed: method = %d",
static_cast<int>(method));
metrics_->oemcrypto_provisioning_method_.SetError(method);
return CdmResponseType(GET_PROVISIONING_METHOD_ERROR);
}
*token_type = type;
return CdmResponseType(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_InitializeAndCheckKeybox(
&needs_keybox_provisioning_),
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) {
CacheVersion();
}
}
void CryptoSession::ReinitializeForTest() {
if (initialized_) {
const OEMCryptoResult status = OEMCrypto_Terminate();
if (OEMCrypto_SUCCESS != status) {
LOGE("OEMCrypto_Terminate failed: %d", status);
return;
}
initialized_ = false;
// Tables will be reinitialized by tests when needed.
usage_table_l1_.reset();
usage_table_l3_.reset();
}
// Give up if we cannot initialize at all.
const OEMCryptoResult status = OEMCrypto_Initialize();
if (OEMCrypto_SUCCESS != status) {
LOGE("OEMCrypto_Initialize failed: %d", status);
return;
}
OEMCrypto_SetMaxAPIVersion(ODK_MAJOR_VERSION);
OEMCrypto_EnterTestMode();
initialized_ = true;
// For integration and unit tests we will install a test keybox and do not
// need to do keybox provisioning.
needs_keybox_provisioning_ = false;
// This was skipped in Init because initialization failed.
CacheVersion();
}
void CryptoSession::CacheVersion() {
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());
if (needs_keybox_provisioning_) {
WithStaticFieldWriteLock("SystemFallbackPolicy", [&] {
if (!okp_fallback_policy_l1_) {
LOGD("OEMCrypto needs keybox provisioning");
// Only create once. Possible that OEMCrypto is initialized
// and terminated many times over the life cycle of the OTA
// keybox provisioning process.
okp_fallback_policy_l1_ = okp::SystemFallbackPolicy::Create();
if (okp_fallback_policy_l1_)
okp_fallback_policy_l1_->MarkNeedsProvisioning();
}
});
}
}
bool CryptoSession::TryTerminate() {
LOGV("Terminating crypto session");
const bool terminated = 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;
const OEMCryptoResult sts =
WithOecWriteLock("Terminate", [&] { return OEMCrypto_Terminate(); });
if (OEMCrypto_SUCCESS != sts) {
LOGE("OEMCrypto_Terminate failed: status = %d", static_cast<int>(sts));
}
initialized_ = false;
return true;
});
if (terminated) {
UsageTableLock lock(usage_table_mutex_);
usage_table_l1_.reset();
usage_table_l3_.reset();
}
return terminated;
}
void CryptoSession::DisableDelayedTermination() {
LOGV("Disable delayed termination");
WithStaticFieldWriteLock("DisableDelayedTermination",
[&] { termination_counter_ = 0; });
}
bool CryptoSession::SetUpUsageTable(
RequestedSecurityLevel requested_security_level) {
if (usage_table_ != nullptr) {
LOGE("Usage table is already set up for the current crypto session");
return false;
}
const CdmSecurityLevel security_level =
GetSecurityLevel(requested_security_level);
if (security_level != kSecurityLevelL1 &&
security_level != kSecurityLevelL3) {
LOGD("Unsupported security level for usage support: security_level = %d",
static_cast<int>(security_level));
return false;
}
// Check if usage support is available.
bool supports_usage_table = false;
if (!HasUsageTableSupport(requested_security_level, &supports_usage_table)) {
metrics_->oemcrypto_usage_table_support_.SetError(
USAGE_INFORMATION_SUPPORT_FAILED);
return false;
}
metrics_->oemcrypto_usage_table_support_.Record(
supports_usage_table ? kUsageEntrySupport : kNonSecureUsageSupport);
if (!supports_usage_table) {
return false;
}
LOGV("Usage table lock: SetUpUsageTable()");
UsageTableLock auto_lock(usage_table_mutex_);
// TODO(b/141350978): Prevent any recursive logic.
// Manipulate only the usage table for the requested security level.
std::unique_ptr<CdmUsageTable>& table =
security_level == kSecurityLevelL1 ? usage_table_l1_ : usage_table_l3_;
if (!table) {
// This may be called twice within the same thread when the table
// is initialized. On the second call |header| will not be null,
// causing this block to be skipped.
table.reset(new CdmUsageTable());
if (!table->Init(security_level, this)) {
LOGE("Failed to initialize and sync usage usage table");
// Must be cleared globally to prevent the next session to be
// opened from using the invalid CdmUsageTable.
table.reset();
return false;
}
}
usage_table_ = table.get();
metrics_->usage_table_header_initial_size_.Record(usage_table_->size());
return true;
}
CdmResponseType CryptoSession::GetTokenFromKeybox(
RequestedSecurityLevel requested_security_level, std::string* key_data) {
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
RETURN_IF_NULL(key_data, PARAMETER_NULL);
LOGV("requested_security_level = %s",
RequestedSecurityLevelToString(requested_security_level));
// Devices with an invalid L1 keybox which support OTA keybox
// provisioning don't have keybox data.
const bool keybox_provisioning_required = WithStaticFieldReadLock(
"GetTokenFromKeybox - keybox_provisioning_required", [&] {
if (requested_security_level_ != kLevelDefault) return false;
return needs_keybox_provisioning_;
});
if (keybox_provisioning_required) return CdmResponseType(NEED_PROVISIONING);
size_t key_data_length = KEYBOX_KEY_DATA_SIZE;
key_data->assign(key_data_length, '\0');
OEMCryptoResult status;
WithOecReadLock("GetTokenFromKeybox", [&] {
M_TIME(status = OEMCrypto_GetKeyData(
reinterpret_cast<uint8_t*>(&key_data->front()), &key_data_length,
requested_security_level),
metrics_, oemcrypto_get_key_data_, status,
metrics::Pow2Bucket(key_data_length));
});
if (OEMCrypto_SUCCESS == status) {
key_data->resize(key_data_length);
return CdmResponseType(NO_ERROR);
}
key_data->clear();
return MapOEMCryptoResult(status, GET_TOKEN_FROM_KEYBOX_ERROR,
"GetTokenFromKeybox");
}
CdmResponseType CryptoSession::GetTokenFromOemCert(
RequestedSecurityLevel requested_security_level, std::string* oem_cert) {
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
RETURN_IF_NULL(oem_cert, PARAMETER_NULL);
LOGV("requested_security_level = %s",
RequestedSecurityLevelToString(requested_security_level));
const bool cache_success =
WithOecSessionLock("GetTokenFromOemCert - check cached", [&] {
if (oem_token_.empty()) {
return false;
}
oem_cert->assign(oem_token_);
return true;
});
if (cache_success) return CdmResponseType(NO_ERROR);
size_t oem_cert_length = CERTIFICATE_DATA_SIZE;
oem_cert->assign(oem_cert_length, '\0');
OEMCryptoResult status =
WithOecReadLock("GetTokenFromOemCert - attempt 1", [&] {
return OEMCrypto_GetOEMPublicCertificate(
reinterpret_cast<uint8_t*>(&oem_cert->front()), &oem_cert_length,
requested_security_level);
});
metrics_->oemcrypto_get_oem_public_certificate_.Increment(status);
if (status == OEMCrypto_ERROR_SHORT_BUFFER) {
oem_cert->assign(oem_cert_length, '\0');
status = WithOecReadLock("GetTokenFromOemCert - attempt 2", [&] {
return OEMCrypto_GetOEMPublicCertificate(
reinterpret_cast<uint8_t*>(&oem_cert->front()), &oem_cert_length,
requested_security_level);
});
metrics_->oemcrypto_get_oem_public_certificate_.Increment(status);
}
if (status == OEMCrypto_SUCCESS) {
oem_cert->resize(oem_cert_length);
WithOecSessionLock("GetTokenFromOemCert - set cache",
[&] { oem_token_ = *oem_cert; });
return CdmResponseType(NO_ERROR);
}
oem_cert->clear();
return MapOEMCryptoResult(status, GET_TOKEN_FROM_OEM_CERT_ERROR,
"GetTokenFromOemCert");
}
CdmResponseType CryptoSession::GetProvisioningToken(
std::string* token, std::string* additional_token) {
RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN);
return GetProvisioningToken(requested_security_level_, token,
additional_token);
}
CdmResponseType CryptoSession::GetProvisioningToken(
RequestedSecurityLevel requested_security_level, std::string* token,
std::string* additional_token) {
if (token == nullptr || additional_token == nullptr) {
metrics_->crypto_session_get_token_.Increment(
CdmResponseType(PARAMETER_NULL));
RETURN_IF_NULL(token, PARAMETER_NULL);
RETURN_IF_NULL(additional_token, PARAMETER_NULL);
}
if (!IsInitialized()) {
metrics_->crypto_session_get_token_.Increment(
CdmResponseType(CRYPTO_SESSION_NOT_INITIALIZED));
return CdmResponseType(CRYPTO_SESSION_NOT_INITIALIZED);
}
CdmResponseType status(UNKNOWN_CLIENT_TOKEN_TYPE);
if (pre_provision_token_type_ == kClientTokenKeybox) {
status = GetTokenFromKeybox(requested_security_level, token);
} else if (pre_provision_token_type_ == kClientTokenOemCert) {
status = GetTokenFromOemCert(requested_security_level, token);
} else if (pre_provision_token_type_ == kClientTokenBootCertChain) {
status = GetBootCertificateChain(requested_security_level, token,
additional_token);
} else if (pre_provision_token_type_ ==
kClientTokenDrmCertificateReprovisioning) {
status = GetTokenFromEmbeddedCertificate(token);
}
metrics_->crypto_session_get_token_.Increment(status);
return status;
}
CdmResponseType CryptoSession::GetProvisioning40TokenType(
OEMCrypto_BCCType* bcc_type) {
RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN);
return GetProvisioning40TokenType(requested_security_level_, bcc_type);
}
CdmResponseType CryptoSession::GetProvisioning40TokenType(
RequestedSecurityLevel requested_security_level,
OEMCrypto_BCCType* bcc_type) {
RETURN_IF_NULL(bcc_type, PARAMETER_NULL);
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
OEMCryptoResult sts = WithOecReadLock("GetBCCType", [&] {
return OEMCrypto_GetBCCType(requested_security_level, bcc_type);
});
return MapOEMCryptoResult(sts, UNKNOWN_CLIENT_TOKEN_TYPE,
"GetProvisioning40TokenType");
}
CdmSecurityLevel CryptoSession::GetSecurityLevel() {
LOGV("Getting security level");
RETURN_IF_NOT_OPEN(kSecurityLevelUninitialized);
return GetSecurityLevel(requested_security_level_);
}
CdmSecurityLevel CryptoSession::GetSecurityLevel(
RequestedSecurityLevel requested_security_level) {
LOGV("Getting security level: requested_security_level = %s",
RequestedSecurityLevelToString(requested_security_level));
RETURN_IF_UNINITIALIZED(kSecurityLevelUninitialized);
const OEMCrypto_Security_Level level = WithOecReadLock(
"GetSecurityLevel",
[&] { return OEMCrypto_SecurityLevel(requested_security_level); });
if (level == 0) {
LOGE("Security level is unknown: requested_security_level = %d",
static_cast<int>(requested_security_level));
return kSecurityLevelUnknown;
}
if (level == OEMCrypto_Level1) {
return kSecurityLevelL1;
}
if (level == OEMCrypto_Level2) {
return kSecurityLevelL2;
}
if (level == OEMCrypto_Level3) {
return kSecurityLevelL3;
}
LOGE(
"Ill-formed security level: "
"level = \"L%u\", requested_security_level = %s",
static_cast<unsigned int>(level),
RequestedSecurityLevelToString(requested_security_level));
return kSecurityLevelUnknown;
}
CdmResponseType CryptoSession::GetInternalDeviceUniqueId(
std::string* device_id) {
RETURN_IF_NULL(device_id, PARAMETER_NULL);
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
size_t device_id_length = 64;
device_id->assign(device_id_length, '\0');
OEMCryptoResult sts =
WithOecReadLock("GetInternalDeviceUniqueId Attempt 1", [&] {
return OEMCrypto_GetDeviceID(
reinterpret_cast<uint8_t*>(&device_id->front()), &device_id_length,
requested_security_level_);
});
metrics_->oemcrypto_get_device_id_.Increment(sts);
if (sts == OEMCrypto_ERROR_SHORT_BUFFER) {
device_id->resize(device_id_length, '\0');
sts = WithOecReadLock("GetInternalDeviceUniqueId Attempt 2", [&] {
return OEMCrypto_GetDeviceID(
reinterpret_cast<uint8_t*>(&device_id->front()), &device_id_length,
requested_security_level_);
});
metrics_->oemcrypto_get_device_id_.Increment(sts);
}
// 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->resize(device_id_length);
return CdmResponseType(NO_ERROR);
}
device_id->clear();
if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED &&
pre_provision_token_type_ == kClientTokenOemCert) {
return GetTokenFromOemCert(requested_security_level_, device_id);
}
const bool use_null_device_id = WithStaticFieldReadLock(
"GetInternalDeviceUniqueId() use_null_device_id", [&] {
if (requested_security_level_ != kLevelDefault) return false;
if (!needs_keybox_provisioning_) return false;
if (sts != OEMCrypto_ERROR_KEYBOX_INVALID &&
sts != OEMCrypto_ERROR_NO_DEVICEID) {
// Logging other error for debugging, but null device
// ID should still be returned.
LOGE("Unexpected error: sts = %d", sts);
}
return true;
});
if (use_null_device_id) {
LOGD("Using null device ID");
constexpr size_t kKeyboxDeviceIdLength = 32;
device_id->assign(kKeyboxDeviceIdLength, '\0');
return CdmResponseType(NO_ERROR);
}
return MapOEMCryptoResult(sts, GET_DEVICE_ID_ERROR,
"GetInternalDeviceUniqueId");
}
CdmResponseType CryptoSession::GetExternalDeviceUniqueId(
std::string* device_id) {
RETURN_IF_NULL(device_id, PARAMETER_NULL);
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
const CdmResponseType status = GetInternalDeviceUniqueId(device_id);
if (status != NO_ERROR) return status;
if (device_id->size() > kMaxExternalDeviceIdLength) {
// To keep the size of the value passed back to the application down, hash
// the large OEM Public Cert to a smaller value.
*device_id = Sha256Hash(*device_id);
}
return CdmResponseType(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(RequestedSecurityLevel security_level,
uint32_t* version) {
LOGV("Getting API version: security_level = %s",
RequestedSecurityLevelToString(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(RequestedSecurityLevel security_level,
uint32_t* minor_version) {
LOGV("Getting API minor version: security_level = %s",
RequestedSecurityLevelToString(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::GetCachedSystemId(uint32_t* system_id) {
RETURN_IF_NULL(system_id, false);
RETURN_IF_NOT_OPEN(false);
if (system_id_ == NULL_SYSTEM_ID) return false;
*system_id = system_id_;
return true;
}
void CryptoSession::SetSystemId(uint32_t system_id) {
if (!IsOpen()) return; // Ignore silently.
system_id_ = system_id;
metrics_->crypto_session_system_id_.Record(system_id_);
}
CdmResponseType CryptoSession::GetProvisioningId(std::string* provisioning_id) {
RETURN_IF_NULL(provisioning_id, PARAMETER_NULL);
RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN);
if (pre_provision_token_type_ == kClientTokenOemCert ||
pre_provision_token_type_ == kClientTokenBootCertChain) {
// OEM Cert and BCC 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.
const CdmResponseType status = GetExternalDeviceUniqueId(provisioning_id);
if (status != NO_ERROR) return status;
for (char& c : *provisioning_id) {
c ^= 0xff;
}
return CdmResponseType(NO_ERROR);
}
if (pre_provision_token_type_ == kClientTokenKeybox) {
std::string token;
CdmResponseType status =
GetTokenFromKeybox(requested_security_level_, &token);
if (status != NO_ERROR) return status;
if (token.size() < 24) {
LOGE("Keybox token size too small: %zu", token.size());
return CdmResponseType(KEYBOX_TOKEN_TOO_SHORT);
}
provisioning_id->assign(reinterpret_cast<char*>(&token[8]), 16);
return CdmResponseType(NO_ERROR);
}
LOGE("Unsupported pre-provision token type: %d",
static_cast<int>(pre_provision_token_type_));
return CdmResponseType(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(
RequestedSecurityLevel requested_security_level) {
LOGD("Opening crypto session: requested_security_level = %s",
RequestedSecurityLevelToString(requested_security_level));
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
if (open_) return CdmResponseType(NO_ERROR);
if (!SetUpUsageTable(requested_security_level)) {
// 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.
LOGW("Session opened without a usage table");
}
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;
// Set up request ID
uint64_t request_id_base =
wvutil::CdmRandom::RandomInRange(std::numeric_limits<uint64_t>::max());
uint64_t request_id_index =
request_id_index_source_.fetch_add(1, std::memory_order_relaxed);
request_id_ = wvutil::HexEncode(reinterpret_cast<uint8_t*>(&request_id_base),
sizeof(request_id_base)) +
wvutil::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(requested_security_level_,
oec_session_id_, metrics_));
});
if (!GetApiVersion(&api_version_)) {
LOGE("Failed to get API version");
return CdmResponseType(USAGE_SUPPORT_GET_API_FAILED);
}
return CdmResponseType(NO_ERROR);
}
void CryptoSession::Close() {
LOGV("Closing crypto session: id = %u, open = %s", oec_session_id_,
open_ ? "true" : "false");
if (!open_) return;
const OEMCryptoResult close_sts = WithOecWriteLock(
"Close", [&] { return OEMCrypto_CloseSession(oec_session_id_); });
metrics_->oemcrypto_close_session_.Increment(close_sts);
// Clear cached values.
has_usage_table_support_ = kBooleanUnset;
oem_token_.clear();
system_id_ = NULL_SYSTEM_ID;
pre_provision_token_type_ = kClientTokenUninitialized;
if (close_sts != OEMCrypto_SUCCESS) {
LOGW("OEMCrypto_CloseSession failed: status = %d",
static_cast<int>(close_sts));
}
switch (close_sts) {
case OEMCrypto_SUCCESS:
case OEMCrypto_ERROR_INVALID_SESSION:
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
usage_table_ = nullptr;
open_ = false;
break;
case OEMCrypto_ERROR_CLOSE_SESSION_FAILED:
default:
// empty case
break;
}
}
CdmResponseType CryptoSession::PrepareAndSignLicenseRequest(
const std::string& message, std::string* core_message,
std::string* signature, bool& should_specify_algorithm,
OEMCrypto_SignatureHashAlgorithm& algorithm) {
LOGV("Preparing and signing license request: id = %u", oec_session_id_);
RETURN_IF_NULL(signature, PARAMETER_NULL);
RETURN_IF_NULL(core_message, PARAMETER_NULL);
RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN);
OEMCryptoResult sts;
WithOecSessionLock("GetSignatureHashAlgorithm", [&] {
sts = OEMCrypto_GetSignatureHashAlgorithm(oec_session_id_, &algorithm);
});
metrics_->oemcrypto_get_signature_hash_algorithm_.Increment(sts, algorithm);
if (sts == OEMCrypto_SUCCESS) {
should_specify_algorithm = true;
} else if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) {
should_specify_algorithm = false;
} else {
return MapOEMCryptoResult(sts, GET_SIGNATURE_HASH_ALGORITHM_ERROR_1,
"PrepareAndSignLicenseRequest");
}
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) {
// TODO(b/174412779): Remove when b/170704368 is fixed.
// Temporary workaround. If this error is returned the only way to
// recover is for the app to reprovision.
if (static_cast<int>(sts) == kRsaSsaPssSignatureLengthError) {
LOGE("OEMCrypto PrepareAndSignLicenseRequest result = %d",
static_cast<int>(sts));
return CRYPTO_ERROR(NEED_PROVISIONING, 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 CdmResponseType(NO_ERROR);
}
// TODO(b/174412779): Remove when b/170704368 is fixed.
// Temporary workaround. If this error is returned the only way to
// recover is for the app to reprovision.
if (static_cast<int>(sts) == kRsaSsaPssSignatureLengthError) {
LOGE("OEMCrypto PrepareAndSignLicenseRequest result = %d",
static_cast<int>(sts));
return CRYPTO_ERROR(NEED_PROVISIONING, sts);
}
return MapOEMCryptoResult(sts, GENERATE_SIGNATURE_ERROR,
"PrepareAndSignLicenseRequest");
}
#ifdef HAS_DUAL_KEY
CdmResponseType CryptoSession::UseSecondaryKey(bool dual_key) {
OEMCryptoResult sts;
WithOecSessionLock("UseSecondaryKey", [&] {
sts = OEMCrypto_UseSecondaryKey(oec_session_id_, dual_key);
});
return MapOEMCryptoResult(sts, LOAD_KEY_ERROR, "UseSecondaryKey");
}
#else
CdmResponseType CryptoSession::UseSecondaryKey(bool /* dual_key */) {
return CdmResponseType(NO_ERROR);
}
#endif
CdmResponseType CryptoSession::LoadLicense(const std::string& context,
const std::string& session_key,
const std::string& signed_message,
const std::string& core_message,
const std::string& signature,
CdmLicenseKeyType key_type) {
LOGV("Loading license: id = %u", oec_session_id_);
const std::string combined_message = core_message + signed_message;
OEMCryptoResult sts;
WithOecSessionLock("LoadLicense", [&] {
if (key_type == kLicenseKeyTypeEntitlement &&
key_session_->Type() != KeySession::kEntitlement) {
key_session_.reset(new EntitlementKeySession(requested_security_level_,
oec_session_id_, metrics_));
}
M_TIME(sts = OEMCrypto_LoadLicense(
oec_session_id_,
reinterpret_cast<const uint8_t*>(context.data()), context.size(),
reinterpret_cast<const uint8_t*>(session_key.data()),
session_key.size(),
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 CdmResponseType(KEY_ADDED);
case OEMCrypto_ERROR_BUFFER_TOO_LARGE:
LOGE("LoadLicense buffer too large: size = %zu", combined_message.size());
return CRYPTO_ERROR(LOAD_LICENSE_ERROR, sts);
case OEMCrypto_ERROR_TOO_MANY_KEYS:
LOGE("Too many keys in license");
return CRYPTO_ERROR(INSUFFICIENT_CRYPTO_RESOURCES, sts);
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 CdmResponseType(PARAMETER_NULL);
}
if (core_message == nullptr) {
LOGE("Output parameter |core_message| not provided");
return CdmResponseType(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 CdmResponseType(NO_ERROR);
}
return MapOEMCryptoResult(sts, GENERATE_SIGNATURE_ERROR,
"PrepareAndSignRenewalRequest");
}
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 CdmResponseType(KEY_ADDED);
}
if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE) {
LOGE("Buffer too large: size = %zu", combined_message.size());
return CRYPTO_ERROR(LOAD_RENEWAL_ERROR, sts);
}
return MapOEMCryptoResult(sts, LOAD_RENEWAL_ERROR, "LoadRenewal");
}
CdmResponseType CryptoSession::PrepareAndSignProvisioningRequest(
const std::string& message, std::string* core_message,
std::string* signature, bool& should_specify_algorithm,
OEMCrypto_SignatureHashAlgorithm& algorithm) {
LOGV("Preparing and signing provisioning request: id = %u", oec_session_id_);
if (signature == nullptr) {
LOGE("Output parameter |signature| not provided");
return CdmResponseType(PARAMETER_NULL);
}
if (core_message == nullptr) {
LOGE("Output parameter |core_message| not provided");
return CdmResponseType(PARAMETER_NULL);
}
OEMCryptoResult sts;
// TODO: b/305093063 - Refactor to a switch statement to improve readability
if (pre_provision_token_type_ == kClientTokenKeybox) {
should_specify_algorithm = false;
} else if (pre_provision_token_type_ == kClientTokenOemCert) {
should_specify_algorithm = true;
WithOecSessionLock("LoadOEMPrivateKey", [&] {
sts = OEMCrypto_LoadOEMPrivateKey(oec_session_id_);
});
if (sts != OEMCrypto_SUCCESS) {
return MapOEMCryptoResult(sts, GET_TOKEN_FROM_OEM_CERT_ERROR,
"PrepareAndSignProvisioningRequest");
}
} else if (pre_provision_token_type_ == kClientTokenBootCertChain) {
should_specify_algorithm = true;
// Do nothing here. The key to signing the provisioning 4.0 request for each
// stage has been loaded already when it was generated by OEMCrypto.
} else if (pre_provision_token_type_ ==
kClientTokenDrmCertificateReprovisioning) {
should_specify_algorithm = false;
// Do nothing here. The baked-in certificate used as the token has already
// been loaded when the EncryptedClientId was filled in.
} else {
LOGE("Unknown method %d", pre_provision_token_type_);
return CdmResponseType(UNKNOWN_CLIENT_TOKEN_TYPE);
}
if (should_specify_algorithm) {
WithOecSessionLock("GetSignatureHashAlgorithm", [&] {
sts = OEMCrypto_GetSignatureHashAlgorithm(oec_session_id_, &algorithm);
});
metrics_->oemcrypto_get_signature_hash_algorithm_.Increment(sts, algorithm);
if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) {
should_specify_algorithm = false;
} else if (sts != OEMCrypto_SUCCESS) {
return MapOEMCryptoResult(sts, GET_SIGNATURE_HASH_ALGORITHM_ERROR_3,
"PrepareAndSignProvisioningRequest");
}
}
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 CdmResponseType(NO_ERROR);
}
return MapOEMCryptoResult(sts, GENERATE_SIGNATURE_ERROR,
"PrepareAndSignProvisioningRequest");
}
CdmResponseType CryptoSession::LoadEntitledContentKeys(
const std::vector<CryptoKey>& key_array) {
const OEMCryptoResult sts = WithOecSessionLock(
"LoadEntitledContentKeys",
[&] { return key_session_->LoadEntitledContentKeys(key_array); });
switch (sts) {
case OEMCrypto_SUCCESS:
return CdmResponseType(KEY_ADDED);
case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES:
return CRYPTO_ERROR(INSUFFICIENT_CRYPTO_RESOURCES, sts);
case OEMCrypto_ERROR_INVALID_CONTEXT:
return CRYPTO_ERROR(NOT_AN_ENTITLEMENT_SESSION, sts);
case OEMCrypto_KEY_NOT_ENTITLED:
return CRYPTO_ERROR(NO_MATCHING_ENTITLEMENT_KEY, sts);
case OEMCrypto_ERROR_SESSION_LOST_STATE:
return CRYPTO_ERROR(SESSION_LOST_STATE_ERROR, sts);
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
return CRYPTO_ERROR(SYSTEM_INVALIDATED_ERROR, sts);
default:
return CRYPTO_ERROR(LOAD_ENTITLED_CONTENT_KEYS_ERROR, sts);
}
}
CdmResponseType CryptoSession::LoadCertificatePrivateKey(
const CryptoWrappedKey& private_key) {
const OEMCrypto_PrivateKeyType key_type =
(private_key.type() == CryptoWrappedKey::kEcc)
? OEMCrypto_ECC_Private_Key
: OEMCrypto_RSA_Private_Key;
const std::string& wrapped_key = private_key.key();
LOGV("Loading device DRM key: id = %u", oec_session_id_);
// TODO(b/140813486): determine if cert is RSA or ECC.
OEMCryptoResult sts;
WithOecSessionLock(
"LoadCertificatePrivateKey() calling OEMCrypto_LoadDRMPrivateKey()", [&] {
M_TIME(sts = OEMCrypto_LoadDRMPrivateKey(
oec_session_id_, key_type,
reinterpret_cast<const uint8_t*>(wrapped_key.data()),
wrapped_key.size()),
metrics_, oemcrypto_load_device_drm_key_, sts);
});
return MapOEMCryptoResult(sts, LOAD_DEVICE_RSA_KEY_ERROR,
"LoadCertificatePrivateKey");
}
CdmResponseType CryptoSession::GetBootCertificateChain(
std::string* bcc, std::string* additional_signature) {
RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN);
return GetBootCertificateChain(requested_security_level_, bcc,
additional_signature);
}
CdmResponseType CryptoSession::GetBootCertificateChain(
RequestedSecurityLevel requested_security_level, std::string* bcc,
std::string* additional_signature) {
RETURN_IF_NULL(bcc, PARAMETER_NULL);
RETURN_IF_NULL(additional_signature, PARAMETER_NULL);
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
LOGV("requested_security_level = %s",
RequestedSecurityLevelToString(requested_security_level));
CdmClientTokenType token_type = kClientTokenUninitialized;
const CdmResponseType status =
GetProvisioningMethod(requested_security_level, &token_type);
if (status != NO_ERROR) {
LOGE("Failed to get token type");
return status;
}
if (token_type != kClientTokenBootCertChain) {
return CdmResponseType(
PROVISIONING_TYPE_IS_NOT_BOOT_CERTIFICATE_CHAIN_ERROR);
}
if (requested_security_level != kLevelDefault) {
LOGE("CDM only supports L1 BCC");
return CdmResponseType(NOT_IMPLEMENTED_ERROR);
}
size_t bcc_length = 0;
size_t additional_signature_length = 0;
OEMCryptoResult sts =
WithOecReadLock("GetBootCertificateChain Attempt 1", [&] {
return OEMCrypto_GetBootCertificateChain(nullptr, &bcc_length, nullptr,
&additional_signature_length);
});
if (sts == OEMCrypto_ERROR_SHORT_BUFFER) {
bcc->resize(bcc_length);
additional_signature->resize(additional_signature_length);
sts = WithOecReadLock("GetBootCertificateChain Attempt 2", [&] {
return OEMCrypto_GetBootCertificateChain(
MutableStringDataPointer(bcc), &bcc_length,
MutableStringDataPointer(additional_signature),
&additional_signature_length);
});
}
if (sts != OEMCrypto_SUCCESS) {
return MapOEMCryptoResult(sts, GET_BOOT_CERTIFICATE_CHAIN_ERROR,
"GetBootCertificateChain");
}
bcc->resize(bcc_length);
additional_signature->resize(additional_signature_length);
return CdmResponseType(NO_ERROR);
}
CdmResponseType CryptoSession::GetTokenFromEmbeddedCertificate(
std::string* token) {
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
RETURN_IF_NULL(token, PARAMETER_NULL);
CdmClientTokenType token_type = kClientTokenUninitialized;
const CdmResponseType sts =
GetProvisioningMethod(requested_security_level_, &token_type);
if (sts != NO_ERROR) {
LOGE("Failed to get token type");
return sts;
}
if (token_type != kClientTokenDrmCertificateReprovisioning) {
token->clear();
return CdmResponseType(NO_ERROR);
}
size_t certificate_length = CERTIFICATE_DATA_SIZE;
std::string embedded_certificate(certificate_length, '\0');
OEMCryptoResult status =
WithOecReadLock("GetTokenFromEmbeddedCertificate - attempt 1", [&] {
return OEMCrypto_GetEmbeddedDrmCertificate(
reinterpret_cast<uint8_t*>(&embedded_certificate.front()),
&certificate_length);
});
if (status == OEMCrypto_ERROR_SHORT_BUFFER) {
embedded_certificate.assign(certificate_length, '\0');
status =
WithOecReadLock("GetTokenFromEmbeddedCertificate - attempt 2", [&] {
return OEMCrypto_GetEmbeddedDrmCertificate(
reinterpret_cast<uint8_t*>(&embedded_certificate.front()),
&certificate_length);
});
}
if (status == OEMCrypto_SUCCESS) {
// TODO: b/312782308 - Store embedded certificate as SignedDrmCertificate
video_widevine_client::sdk::HashedFile hashed_file;
video_widevine_client::sdk::File file;
embedded_certificate.resize(certificate_length);
if (hashed_file.ParseFromString(embedded_certificate) &&
file.ParseFromString(hashed_file.file())) {
*token = file.device_certificate().certificate();
return CdmResponseType(NO_ERROR);
}
}
token->clear();
return MapOEMCryptoResult(status, GET_TOKEN_FROM_EMBEDDED_CERT_ERROR,
"GetTokenFromEmbeddedCertificate");
}
CdmResponseType CryptoSession::GetDeviceInformation(
RequestedSecurityLevel requested_security_level, std::string* device_info) {
RETURN_IF_NULL(device_info, PARAMETER_NULL);
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
if (GetSecurityLevel(requested_security_level) != kSecurityLevelL1) {
LOGE("CDM only supports L1 device_info");
return CdmResponseType(NOT_IMPLEMENTED_ERROR);
}
CdmClientTokenType token_type = kClientTokenUninitialized;
const CdmResponseType status =
GetProvisioningMethod(requested_security_level, &token_type);
if (status != NO_ERROR) {
LOGE("Failed to get token type");
return status;
}
if (token_type != kClientTokenBootCertChain) {
return CdmResponseType(
PROVISIONING_TYPE_IS_NOT_BOOT_CERTIFICATE_CHAIN_ERROR);
}
size_t device_info_length = 0;
OEMCryptoResult sts = WithOecReadLock("GetDeviceInformation Attempt 1", [&] {
return OEMCrypto_GetDeviceInformation(nullptr, &device_info_length);
});
if (sts == OEMCrypto_ERROR_SHORT_BUFFER) {
device_info->resize(device_info_length);
sts = WithOecReadLock("GetDeviceInformation Attempt 2", [&] {
return OEMCrypto_GetDeviceInformation(
MutableStringDataPointer(device_info), &device_info_length);
});
}
if (sts != OEMCrypto_SUCCESS) {
LOGE("OEMCrypto_GetDeviceInformation failed: status = %d",
static_cast<int>(sts));
device_info->clear();
return MapOEMCryptoResult(sts, GET_DEVICE_INFORMATION_ERROR,
"GetDeviceInformation");
}
device_info->resize(device_info_length);
return CdmResponseType(NO_ERROR);
}
CdmResponseType CryptoSession::GetDeviceSignedCsrPayload(
RequestedSecurityLevel requested_security_level,
const std::string& challenge, const std::string& device_info,
std::string* signed_csr_payload) {
RETURN_IF_NULL(signed_csr_payload, PARAMETER_NULL);
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
if (GetSecurityLevel(requested_security_level) != kSecurityLevelL1) {
LOGE("CDM only supports L1 CSR payload");
return CdmResponseType(NOT_IMPLEMENTED_ERROR);
}
CdmClientTokenType token_type = kClientTokenUninitialized;
const CdmResponseType status =
GetProvisioningMethod(requested_security_level, &token_type);
if (status != NO_ERROR) {
LOGE("Failed to get token type");
return status;
}
if (token_type != kClientTokenBootCertChain) {
return CdmResponseType(
PROVISIONING_TYPE_IS_NOT_BOOT_CERTIFICATE_CHAIN_ERROR);
}
size_t signed_csr_payload_length = 0;
OEMCryptoResult sts =
WithOecReadLock("GetDeviceSignedCsrPayload Attempt 1", [&] {
return OEMCrypto_GetDeviceSignedCsrPayload(
reinterpret_cast<const uint8_t*>(challenge.data()),
challenge.size(),
reinterpret_cast<const uint8_t*>(device_info.data()),
device_info.size(), nullptr, &signed_csr_payload_length);
});
if (sts == OEMCrypto_ERROR_SHORT_BUFFER) {
signed_csr_payload->resize(signed_csr_payload_length);
sts = WithOecReadLock("GetDeviceSignedCsrPayload Attempt 2", [&] {
return OEMCrypto_GetDeviceSignedCsrPayload(
reinterpret_cast<const uint8_t*>(challenge.data()), challenge.size(),
reinterpret_cast<const uint8_t*>(device_info.data()),
device_info.size(), MutableStringDataPointer(signed_csr_payload),
&signed_csr_payload_length);
});
}
if (sts != OEMCrypto_SUCCESS) {
LOGE("OEMCrypto_GetDeviceSignedCsrPayload failed: status = %d",
static_cast<int>(sts));
signed_csr_payload->clear();
return MapOEMCryptoResult(sts, GET_DEVICE_SIGNED_CSR_PAYLOAD_ERROR,
"GetDeviceSignedCsrPayload");
}
signed_csr_payload->resize(signed_csr_payload_length);
return CdmResponseType(NO_ERROR);
}
CdmResponseType CryptoSession::GenerateCertificateKeyPair(
std::string* public_key, std::string* public_key_signature,
std::string* wrapped_private_key, CryptoWrappedKey::Type* key_type) {
LOGV("Generating certificate key pair: id = %u", oec_session_id_);
RETURN_IF_NULL(public_key, PARAMETER_NULL);
RETURN_IF_NULL(public_key_signature, PARAMETER_NULL);
RETURN_IF_NULL(wrapped_private_key, PARAMETER_NULL);
RETURN_IF_NULL(key_type, PARAMETER_NULL);
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
// Round 1, get the size of all the fields.
size_t public_key_length = 0;
size_t public_key_signature_length = 0;
size_t wrapped_private_key_length = 0;
OEMCrypto_PrivateKeyType oemcrypto_key_type;
OEMCryptoResult status;
WithOecSessionLock("GenerateCertificateKeyPair Attempt 1", [&] {
M_TIME(status = OEMCrypto_GenerateCertificateKeyPair(
oec_session_id_, nullptr, &public_key_length, nullptr,
&public_key_signature_length, nullptr,
&wrapped_private_key_length, &oemcrypto_key_type),
metrics_, oemcrypto_generate_certificate_key_pair_, status);
});
if (status != OEMCrypto_ERROR_SHORT_BUFFER) {
return MapOEMCryptoResult(status, GENERATE_CERTIFICATE_KEY_PAIR_ERROR,
"GenerateCertificateKeyPair");
}
public_key->resize(public_key_length);
public_key_signature->resize(public_key_signature_length);
wrapped_private_key->resize(wrapped_private_key_length);
WithOecSessionLock("GenerateCertificateKeyPair Attempt 2", [&] {
M_TIME(
status = OEMCrypto_GenerateCertificateKeyPair(
oec_session_id_, MutableStringDataPointer(public_key),
&public_key_length, MutableStringDataPointer(public_key_signature),
&public_key_signature_length,
MutableStringDataPointer(wrapped_private_key),
&wrapped_private_key_length, &oemcrypto_key_type),
metrics_, oemcrypto_generate_certificate_key_pair_, status);
});
if (status != OEMCrypto_SUCCESS) {
return MapOEMCryptoResult(status, GENERATE_CERTIFICATE_KEY_PAIR_ERROR,
"GenerateCertificateKeyPair");
}
public_key->resize(public_key_length);
public_key_signature->resize(public_key_signature_length);
wrapped_private_key->resize(wrapped_private_key_length);
if (oemcrypto_key_type == OEMCrypto_RSA_Private_Key) {
*key_type = CryptoWrappedKey::kRsa;
} else if (oemcrypto_key_type == OEMCrypto_ECC_Private_Key) {
*key_type = CryptoWrappedKey::kEcc;
} else {
LOGE("Unexpected key type returned from GenerateCertificateKeyPair: %d",
static_cast<int>(oemcrypto_key_type));
// TODO(b/261185349): add OEMCrypto_PrivateKeyType to CdmResponseType
return CdmResponseType(GENERATE_CERTIFICATE_KEY_PAIR_UNKNOWN_TYPE_ERROR);
}
return CdmResponseType(NO_ERROR);
}
CdmResponseType CryptoSession::LoadOemCertificatePrivateKey(
const CryptoWrappedKey& private_key) {
LOGV("Load OEM cert and private key: id = %u", oec_session_id_);
const OEMCrypto_PrivateKeyType key_type =
(private_key.type() == CryptoWrappedKey::kEcc)
? OEMCrypto_ECC_Private_Key
: OEMCrypto_RSA_Private_Key;
const std::string& wrapped_private_key = private_key.key();
OEMCryptoResult status;
WithOecSessionLock("InstallOemPrivateKey", [&] {
M_TIME(status = OEMCrypto_InstallOemPrivateKey(
oec_session_id_, key_type,
reinterpret_cast<const uint8_t*>(wrapped_private_key.data()),
wrapped_private_key.size()),
metrics_, oemcrypto_install_oem_private_key_, status);
});
return MapOEMCryptoResult(status, LOAD_OEM_CERTIFICATE_PRIVATE_KEY_ERROR,
"InstallOemPrivateKey");
}
// Private.
CdmResponseType CryptoSession::SelectKey(const std::string& key_id,
CdmCipherMode cipher_mode) {
RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN);
const OEMCryptoResult sts = WithOecSessionLock("SelectKey", [&] {
RETURN_IF_NULL(key_session_, OEMCrypto_ERROR_INVALID_SESSION);
return key_session_->SelectKey(key_id, cipher_mode);
});
if (sts != last_select_key_error_) {
// Only log errors on first occurrence. Calls to SelectKey from
// the app are typically queued. If an error occurs, then it is
// expected that multiple failures will occur before the app is
// notified and stops queueing calls to the CDM.
last_select_key_error_ = sts;
if (sts == OEMCrypto_ERROR_INSUFFICIENT_HDCP ||
sts == OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION) {
const char* status_message = OemCryptoResultToString(sts);
HdcpCapability current_hdcp_level;
HdcpCapability max_hdcp_level;
const CdmResponseType hdcp_status =
GetHdcpCapabilities(&current_hdcp_level, &max_hdcp_level);
const char* current_hdcp_message = kUnavailableHdcpLevel;
const char* max_hdcp_message = kUnavailableHdcpLevel;
if (hdcp_status == NO_ERROR) {
current_hdcp_message = HdcpCapabilityToString(current_hdcp_level);
max_hdcp_message = HdcpCapabilityToString(max_hdcp_level);
}
LOGE(
"oec_session_id = %u, security_level = %s, "
"status = %s, current_hdcp_level = %s, "
"max_hdcp_level = %s",
oec_session_id_,
RequestedSecurityLevelToString(requested_security_level_),
status_message, current_hdcp_message, max_hdcp_message);
}
}
switch (sts) {
// SelectKey errors.
case OEMCrypto_SUCCESS:
return CdmResponseType(NO_ERROR);
case OEMCrypto_ERROR_KEY_EXPIRED:
return CRYPTO_ERROR(NEED_KEY, sts);
case OEMCrypto_ERROR_INVALID_SESSION:
return CRYPTO_ERROR(INVALID_SESSION_1, sts);
case OEMCrypto_ERROR_NO_DEVICE_KEY:
return CRYPTO_ERROR(NO_DEVICE_KEY_1, sts);
case OEMCrypto_ERROR_NO_CONTENT_KEY:
return CRYPTO_ERROR(NO_CONTENT_KEY_2, sts);
case OEMCrypto_ERROR_CONTROL_INVALID:
case OEMCrypto_ERROR_KEYBOX_INVALID:
return CRYPTO_ERROR(UNKNOWN_SELECT_KEY_ERROR_2, sts);
case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES:
return CRYPTO_ERROR(INSUFFICIENT_CRYPTO_RESOURCES, sts);
case OEMCrypto_ERROR_UNKNOWN_FAILURE:
return CRYPTO_ERROR(UNKNOWN_SELECT_KEY_ERROR_1, sts);
case OEMCrypto_ERROR_ANALOG_OUTPUT:
return CRYPTO_ERROR(ANALOG_OUTPUT_ERROR, sts);
case OEMCrypto_ERROR_INSUFFICIENT_HDCP:
return CRYPTO_ERROR(INSUFFICIENT_OUTPUT_PROTECTION, sts);
// LoadEntitledContentKeys errors.
// |key_session_| may make calls to OEMCrypto_LoadEntitledContentKeys
// if the key selected has not yet been loaded.
case OEMCrypto_ERROR_INVALID_CONTEXT:
return CRYPTO_ERROR(NOT_AN_ENTITLEMENT_SESSION, sts);
case OEMCrypto_KEY_NOT_ENTITLED:
return CRYPTO_ERROR(NO_MATCHING_ENTITLEMENT_KEY, sts);
// Obsolete errors.
case OEMCrypto_KEY_NOT_LOADED:
return CRYPTO_ERROR(NO_CONTENT_KEY_3, sts);
// Catch all else.
default:
return MapOEMCryptoResult(sts, UNKNOWN_SELECT_KEY_ERROR_2, "SelectKey");
}
}
CdmResponseType CryptoSession::GenerateRsaSignature(const std::string& message,
std::string* signature,
RSA_Padding_Scheme scheme) {
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, scheme),
metrics_, oemcrypto_generate_rsa_signature_, sts,
metrics::Pow2Bucket(length));
});
if (OEMCrypto_SUCCESS == sts) {
// Trim signature buffer and done
signature->resize(length);
return CdmResponseType(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 < wvutil::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 CdmResponseType(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 CdmResponseType(SECURE_BUFFER_REQUIRED);
}
if (params.samples.size() == 0)
return CdmResponseType(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 CdmResponseType(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.clear_buffer =
static_cast<uint8_t*>(sample.decrypt_buffer) +
sample.decrypt_buffer_offset;
output_descriptor.buffer.clear.clear_buffer_length =
sample.decrypt_buffer_size - sample.decrypt_buffer_offset;
break;
case OEMCrypto_BufferType_Secure:
output_descriptor.buffer.secure.secure_buffer = sample.decrypt_buffer;
output_descriptor.buffer.secure.offset = sample.decrypt_buffer_offset;
output_descriptor.buffer.secure.secure_buffer_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 CdmResponseType(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 CdmResponseType(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, is_any_sample_protected);
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 ||
sts == OEMCrypto_ERROR_INSUFFICIENT_HDCP) {
const char* status_message = OemCryptoResultToString(sts);
HdcpCapability current_hdcp_level;
HdcpCapability max_hdcp_level;
const CdmResponseType hdcp_status =
GetHdcpCapabilities(&current_hdcp_level, &max_hdcp_level);
const char* current_hdcp_message = kUnavailableHdcpLevel;
const char* max_hdcp_message = kUnavailableHdcpLevel;
if (hdcp_status == NO_ERROR) {
current_hdcp_message = HdcpCapabilityToString(current_hdcp_level);
max_hdcp_message = HdcpCapabilityToString(max_hdcp_level);
}
LOGE(
"OEMCrypto_DecryptCENC failed: oec_session_id = %u, "
"security_level = %s, status = %s, current_hdcp_level = %s, "
"max_hdcp_level = %s",
oec_session_id_,
RequestedSecurityLevelToString(requested_security_level_),
status_message, current_hdcp_message, max_hdcp_message);
} else {
LOGE(
"OEMCrypto_DecryptCENC failed: oec_session_id = %u, "
"security_level = %s, status = %d",
oec_session_id_,
RequestedSecurityLevelToString(requested_security_level_),
static_cast<int>(sts));
}
}
switch (sts) {
case OEMCrypto_SUCCESS:
case OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION:
return CdmResponseType(NO_ERROR);
case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES:
return CRYPTO_ERROR(INSUFFICIENT_CRYPTO_RESOURCES, sts);
case OEMCrypto_ERROR_KEY_EXPIRED:
return CRYPTO_ERROR(NEED_KEY, sts);
case OEMCrypto_ERROR_INVALID_SESSION:
return CRYPTO_ERROR(INVALID_SESSION_2, sts);
case OEMCrypto_ERROR_DECRYPT_FAILED:
case OEMCrypto_ERROR_UNKNOWN_FAILURE:
return CRYPTO_ERROR(DECRYPT_ERROR, sts);
case OEMCrypto_ERROR_INSUFFICIENT_HDCP:
return CRYPTO_ERROR(INSUFFICIENT_OUTPUT_PROTECTION, sts);
case OEMCrypto_ERROR_ANALOG_OUTPUT:
return CRYPTO_ERROR(ANALOG_OUTPUT_ERROR, sts);
case OEMCrypto_ERROR_OUTPUT_TOO_LARGE:
return CRYPTO_ERROR(OUTPUT_TOO_LARGE_ERROR, sts);
case OEMCrypto_ERROR_SESSION_LOST_STATE:
return CRYPTO_ERROR(SESSION_LOST_STATE_ERROR, sts);
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
return CRYPTO_ERROR(SYSTEM_INVALIDATED_ERROR, sts);
default:
return CRYPTO_ERROR(UNKNOWN_ERROR, sts);
}
}
bool CryptoSession::HasUsageTableSupport(bool* has_support) {
RETURN_IF_NOT_OPEN(false);
RETURN_IF_NULL(has_support, false);
return WithOecReadLock("HasUsageTableSupport", [&] {
// Use cached value if set.
if (has_usage_table_support_ != kBooleanUnset) {
*has_support = (has_usage_table_support_ == kBooleanTrue);
return true;
}
if (!HasUsageTableSupportInternal(requested_security_level_, has_support)) {
return false;
}
// Cache result if successful.
has_usage_table_support_ = (*has_support ? kBooleanTrue : kBooleanFalse);
return true;
});
}
bool CryptoSession::HasUsageTableSupport(
RequestedSecurityLevel requested_security_level, bool* has_support) {
RETURN_IF_UNINITIALIZED(false);
RETURN_IF_NULL(has_support, false);
return WithOecReadLock("HasUsageTableSupport", [&] {
return HasUsageTableSupportInternal(requested_security_level, has_support);
});
}
bool CryptoSession::HasUsageTableSupportInternal(
RequestedSecurityLevel requested_security_level, bool* has_support) {
LOGV("requested_security_level = %s",
RequestedSecurityLevelToString(requested_security_level));
*has_support = WithOecReadLock("HasUsageTableSupport", [&] {
return OEMCrypto_SupportsUsageTable(requested_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 CdmResponseType(NO_ERROR);
case OEMCrypto_ERROR_INVALID_CONTEXT:
return CRYPTO_ERROR(KEY_CANCELED, status);
case OEMCrypto_ERROR_SESSION_LOST_STATE:
return CRYPTO_ERROR(SESSION_LOST_STATE_ERROR, status);
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
return CRYPTO_ERROR(SYSTEM_INVALIDATED_ERROR, status);
default:
return CRYPTO_ERROR(DEACTIVATE_USAGE_ENTRY_ERROR, status);
}
}
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());
wvutil::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 CdmResponseType(
NO_ERROR); // usage report available but no duration information
}
if (kUnused == pst_report.status()) {
*usage_duration_status = kUsageDurationPlaybackNotBegun;
return CdmResponseType(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: %" PRId64 "\n",
pst_report.seconds_since_license_received());
LOGV("OEMCrypto_PST_Report.seconds_since_first_decrypt: %" PRId64 "\n",
pst_report.seconds_since_first_decrypt());
LOGV("OEMCrypto_PST_Report.seconds_since_last_decrypt: %" PRId64 "\n",
pst_report.seconds_since_last_decrypt());
LOGV("OEMCrypto_PST_Report: %s\n", wvutil::b2a_hex(*usage_report).c_str());
if (kInactiveUnused == pst_report.status()) {
*usage_duration_status = kUsageDurationPlaybackNotBegun;
return CdmResponseType(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 CdmResponseType(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 CdmResponseType(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& request, 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 CdmResponseType(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*>(request.data()), request.size(),
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*>(request.data()), request.size(),
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);
});
if (status == OEMCrypto_SUCCESS) {
wrapped_private_key->resize(wrapped_private_key_length);
return CdmResponseType(NO_ERROR);
}
wrapped_private_key->clear();
return MapOEMCryptoResult(status, LOAD_PROVISIONING_ERROR,
"LoadProvisioning");
}
CdmResponseType CryptoSession::LoadProvisioningCast(
const std::string& derivation_key, const std::string& request,
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 CdmResponseType(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("LoadProvisioningCast Attempt 1", [&] {
M_TIME(status = OEMCrypto_LoadProvisioningCast(
oec_session_id_,
reinterpret_cast<const uint8_t*>(derivation_key.data()),
derivation_key.size(),
reinterpret_cast<const uint8_t*>(request.data()), request.size(),
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,
"LoadProvisioningCast");
}
wrapped_private_key->resize(wrapped_private_key_length);
WithOecSessionLock("LoadProvisioningCast Attempt 2", [&] {
M_TIME(status = OEMCrypto_LoadProvisioningCast(
oec_session_id_,
reinterpret_cast<const uint8_t*>(derivation_key.data()),
derivation_key.size(),
reinterpret_cast<const uint8_t*>(request.data()), request.size(),
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);
});
if (status == OEMCrypto_SUCCESS) {
wrapped_private_key->resize(wrapped_private_key_length);
return CdmResponseType(NO_ERROR);
}
wrapped_private_key->clear();
return MapOEMCryptoResult(status, LOAD_PROVISIONING_ERROR,
"LoadProvisioningCast");
}
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(
RequestedSecurityLevel security_level, HdcpCapability* current,
HdcpCapability* max) {
LOGV("Getting HDCP capabilities: id = %u, security_level = %s",
oec_session_id_, RequestedSecurityLevelToString(security_level));
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
RETURN_IF_NULL(current, PARAMETER_NULL);
RETURN_IF_NULL(max, PARAMETER_NULL);
const OEMCryptoResult status = WithOecReadLock("GetHdcpCapabilities", [&] {
return 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);
const uint32_t oec_support =
WithOecReadLock("GetSupportedCertificateTypes", [&] {
return 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;
support->ecc_secp256r1 = oec_support & OEMCrypto_Supports_ECC_secp256r1;
support->ecc_secp384r1 = oec_support & OEMCrypto_Supports_ECC_secp384r1;
support->ecc_secp521r1 = oec_support & OEMCrypto_Supports_ECC_secp521r1;
return true;
}
CdmResponseType CryptoSession::GetNumberOfOpenSessions(
RequestedSecurityLevel security_level, size_t* count) {
LOGV("Getting number of open sessions: id = %u, security_level = %s",
oec_session_id_, RequestedSecurityLevelToString(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(
RequestedSecurityLevel security_level, size_t* max) {
LOGV("Getting max number of sessions: id = %u, security_level = %s",
oec_session_id_, RequestedSecurityLevelToString(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);
const OEMCryptoResult status = WithOecReadLock("GetSrmVersion", [&] {
return OEMCrypto_GetCurrentSRMVersion(srm_version);
});
// SRM is an optional feature. Whether it is implemented is up to the
// discretion of OEMs. OEMs may implement this method, but SRM is not
// required if there is only a local display, as such no SRM version
// is available/reportable. |srm_version| is only set if SUCCESS is
// returned.
switch (status) {
case OEMCrypto_SUCCESS:
return CdmResponseType(NO_ERROR);
case OEMCrypto_LOCAL_DISPLAY_ONLY:
LOGD("No SRM: Local display only");
return CRYPTO_ERROR(NO_SRM_VERSION, status);
case OEMCrypto_ERROR_NOT_IMPLEMENTED:
LOGD("No SRM: Not implemented");
return CRYPTO_ERROR(NO_SRM_VERSION, status);
default:
return MapOEMCryptoResult(status, GET_SRM_VERSION_ERROR,
"GetCurrentSRMVersion");
}
}
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(RequestedSecurityLevel security_level,
uint32_t* tier) {
LOGV("Getting resource rating tier: security_level = %s",
RequestedSecurityLevelToString(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(RequestedSecurityLevel security_level,
std::string* info) {
LOGV("Getting build information: security_level = %s",
RequestedSecurityLevelToString(security_level));
RETURN_IF_UNINITIALIZED(false);
RETURN_IF_NULL(info, false);
size_t info_length = 128;
info->assign(info_length, '\0');
OEMCryptoResult result = WithOecReadLock("GetBuildInformation", [&] {
return OEMCrypto_BuildInformation(&info->front(), &info_length,
security_level);
});
if (result == OEMCrypto_ERROR_SHORT_BUFFER) {
info->assign(info_length, '\0');
result = WithOecReadLock("GetBuildInformation Attempt 2", [&] {
return OEMCrypto_BuildInformation(&info->front(), &info_length,
security_level);
});
}
if (result != OEMCrypto_SUCCESS) {
LOGE("GetBuildInformation failed: result = %d", result);
info->clear();
return false;
}
info->resize(info_length);
return true;
}
bool CryptoSession::GetWatermarkingSupport(CdmWatermarkingSupport* support) {
RETURN_IF_NOT_OPEN(false);
return GetWatermarkingSupport(requested_security_level_, support);
}
bool CryptoSession::GetWatermarkingSupport(
RequestedSecurityLevel security_level, CdmWatermarkingSupport* support) {
LOGV("security_level = %s", RequestedSecurityLevelToString(security_level));
RETURN_IF_UNINITIALIZED(false);
RETURN_IF_NULL(support, false);
const OEMCrypto_WatermarkingSupport oec_support = WithOecReadLock(
"GetWatermarkingSupport",
[&] { return OEMCrypto_GetWatermarkingSupport(security_level); });
switch (oec_support) {
case OEMCrypto_WatermarkingNotSupported:
*support = kWatermarkingNotSupported;
break;
case OEMCrypto_WatermarkingConfigurable:
*support = kWatermarkingConfigurable;
break;
case OEMCrypto_WatermarkingAlwaysOn:
*support = kWatermarkingAlwaysOn;
break;
case OEMCrypto_WatermarkingError:
default:
LOGE("GetWatermarkingSupport error: security_level = %s, result = %d",
RequestedSecurityLevelToString(security_level),
static_cast<int>(oec_support));
metrics_->oemcrypto_watermarking_support_.SetError(oec_support);
return false;
}
metrics_->oemcrypto_watermarking_support_.Record(oec_support);
return true;
}
bool CryptoSession::GetProductionReadiness(CdmProductionReadiness* readiness) {
RETURN_IF_NOT_OPEN(false);
return GetProductionReadiness(requested_security_level_, readiness);
}
bool CryptoSession::GetProductionReadiness(
RequestedSecurityLevel security_level, CdmProductionReadiness* readiness) {
LOGV("security_level = %s", RequestedSecurityLevelToString(security_level));
RETURN_IF_UNINITIALIZED(false);
RETURN_IF_NULL(readiness, false);
const OEMCryptoResult result = WithOecReadLock("GetProductionReadiness", [&] {
return OEMCrypto_ProductionReady(security_level);
});
metrics_->oemcrypto_production_readiness_.Record(result);
switch (result) {
case OEMCrypto_SUCCESS:
*readiness = kProductionReadinessTrue;
break;
case OEMCrypto_ERROR_NOT_IMPLEMENTED:
*readiness = kProductionReadinessUnknown;
break;
case OEMCrypto_ERROR_UNKNOWN_FAILURE:
default: // Other vendor-defined codes indicate not production ready.
LOGD("Not production ready: security_level = %s, result = %d",
RequestedSecurityLevelToString(security_level),
static_cast<int>(result));
*readiness = kProductionReadinessFalse;
break;
}
return true;
}
bool CryptoSession::GetMaximumUsageTableEntries(
RequestedSecurityLevel security_level, size_t* number_of_entries) {
LOGV("Getting maximum usage table entries: security_level = %s",
RequestedSecurityLevelToString(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(
static_cast<uint32_t>(*number_of_entries));
if (*number_of_entries == 0) {
// Special value, indicating that the table size is not directly
// limited.
return true;
}
return *number_of_entries >= kMinimumUsageTableEntriesSupported;
}
bool CryptoSession::GetDecryptHashSupport(RequestedSecurityLevel 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;
case OEMCrypto_ERROR_NOT_IMPLEMENTED:
*decrypt_hash_support = OEMCrypto_Hash_Not_Supported;
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;
RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN);
if (hash.size() != sizeof(uint32_t)) {
LOGE("Unsupported hash size: hash_size = %zu, expected = %zu", hash.size(),
sizeof(uint32_t));
return CdmResponseType(UNKNOWN_ERROR);
}
WithOecSessionLock("SetDecryptHash", [&] {
const uint32_t crc32 = *reinterpret_cast<const uint32_t*>(hash.data());
sts = OEMCrypto_SetDecryptHash(oec_session_id_, frame_number, crc32);
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 CdmResponseType(NO_ERROR);
case OEMCrypto_ERROR_SESSION_LOST_STATE:
return CRYPTO_ERROR(SESSION_LOST_STATE_ERROR, sts);
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
return CRYPTO_ERROR(SYSTEM_INVALIDATED_ERROR, sts);
case OEMCrypto_ERROR_UNKNOWN_FAILURE:
case OEMCrypto_ERROR_NOT_IMPLEMENTED:
default:
return CRYPTO_ERROR(GET_DECRYPT_HASH_ERROR, sts);
}
}
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 = OEMCrypto_AES_CBC_128_NO_PADDING;
if (iv.size() != GenericEncryptionBlockSize(algorithm) ||
!GetGenericEncryptionAlgorithm(algorithm, &oec_algorithm)) {
return CdmResponseType(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", [&] {
sts =
key_session_->GenericEncrypt(in_buffer, iv, oec_algorithm, out_buffer);
});
if (OEMCrypto_SUCCESS != sts) {
LOGE("OEMCrypto_Generic_Encrypt failed: status = %d",
static_cast<int>(sts));
}
switch (sts) {
case OEMCrypto_SUCCESS:
return CdmResponseType(NO_ERROR);
case OEMCrypto_ERROR_KEY_EXPIRED:
return CRYPTO_ERROR(NEED_KEY, sts);
case OEMCrypto_ERROR_NO_CONTENT_KEY:
case OEMCrypto_KEY_NOT_LOADED: // obsolete in v15.
return CRYPTO_ERROR(KEY_NOT_FOUND_3, sts);
case OEMCrypto_ERROR_OUTPUT_TOO_LARGE:
return CRYPTO_ERROR(OUTPUT_TOO_LARGE_ERROR, sts);
case OEMCrypto_ERROR_SESSION_LOST_STATE:
return CRYPTO_ERROR(SESSION_LOST_STATE_ERROR, sts);
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
return CRYPTO_ERROR(SYSTEM_INVALIDATED_ERROR, sts);
default:
return CRYPTO_ERROR(UNKNOWN_ERROR, sts);
}
}
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 = OEMCrypto_AES_CBC_128_NO_PADDING;
if (iv.size() != GenericEncryptionBlockSize(algorithm) ||
!GetGenericEncryptionAlgorithm(algorithm, &oec_algorithm)) {
return CdmResponseType(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", [&] {
sts =
key_session_->GenericDecrypt(in_buffer, iv, oec_algorithm, out_buffer);
});
if (OEMCrypto_SUCCESS != sts) {
LOGE("OEMCrypto_Generic_Decrypt failed: status = %d",
static_cast<int>(sts));
}
switch (sts) {
case OEMCrypto_SUCCESS:
return CdmResponseType(NO_ERROR);
case OEMCrypto_ERROR_KEY_EXPIRED:
return CRYPTO_ERROR(NEED_KEY, sts);
case OEMCrypto_ERROR_NO_CONTENT_KEY:
case OEMCrypto_KEY_NOT_LOADED: // obsolete in v15.
return CRYPTO_ERROR(KEY_NOT_FOUND_4, sts);
case OEMCrypto_ERROR_OUTPUT_TOO_LARGE:
return CRYPTO_ERROR(OUTPUT_TOO_LARGE_ERROR, sts);
case OEMCrypto_ERROR_SESSION_LOST_STATE:
return CRYPTO_ERROR(SESSION_LOST_STATE_ERROR, sts);
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
return CRYPTO_ERROR(SYSTEM_INVALIDATED_ERROR, sts);
default:
return CRYPTO_ERROR(UNKNOWN_ERROR, sts);
}
}
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 = OEMCrypto_HMAC_SHA256;
if (!GetGenericSigningAlgorithm(algorithm, &oec_algorithm)) {
return CdmResponseType(INVALID_PARAMETERS_ENG_15);
}
// 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("GenericSign", [&] {
sts = key_session_->GenericSign(message, oec_algorithm, signature);
});
switch (sts) {
case OEMCrypto_SUCCESS:
return CdmResponseType(NO_ERROR);
case OEMCrypto_ERROR_KEY_EXPIRED:
return CRYPTO_ERROR(NEED_KEY, sts);
case OEMCrypto_ERROR_NO_CONTENT_KEY:
case OEMCrypto_KEY_NOT_LOADED: // obsolete in v15.
return CRYPTO_ERROR(KEY_NOT_FOUND_5, sts);
case OEMCrypto_ERROR_OUTPUT_TOO_LARGE:
return CRYPTO_ERROR(OUTPUT_TOO_LARGE_ERROR, sts);
case OEMCrypto_ERROR_SESSION_LOST_STATE:
return CRYPTO_ERROR(SESSION_LOST_STATE_ERROR, sts);
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
return CRYPTO_ERROR(SYSTEM_INVALIDATED_ERROR, sts);
default:
return CRYPTO_ERROR(UNKNOWN_ERROR, sts);
}
}
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 = OEMCrypto_HMAC_SHA256;
if (!GetGenericSigningAlgorithm(algorithm, &oec_algorithm)) {
return CdmResponseType(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", [&] {
sts = key_session_->GenericVerify(message, oec_algorithm, signature);
});
if (OEMCrypto_SUCCESS != sts) {
LOGE("OEMCrypto_Generic_Verify failed: status = %d", static_cast<int>(sts));
}
switch (sts) {
case OEMCrypto_SUCCESS:
return CdmResponseType(NO_ERROR);
case OEMCrypto_ERROR_KEY_EXPIRED:
return CRYPTO_ERROR(NEED_KEY, sts);
case OEMCrypto_ERROR_NO_CONTENT_KEY:
case OEMCrypto_KEY_NOT_LOADED: // obsolete in v15.
return CRYPTO_ERROR(KEY_NOT_FOUND_6, sts);
case OEMCrypto_ERROR_OUTPUT_TOO_LARGE:
return CRYPTO_ERROR(OUTPUT_TOO_LARGE_ERROR, sts);
case OEMCrypto_ERROR_SESSION_LOST_STATE:
return CRYPTO_ERROR(SESSION_LOST_STATE_ERROR, sts);
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
return CRYPTO_ERROR(SYSTEM_INVALIDATED_ERROR, sts);
default:
return CRYPTO_ERROR(UNKNOWN_ERROR, sts);
}
}
CdmResponseType CryptoSession::CreateUsageTableHeader(
RequestedSecurityLevel requested_security_level,
UsageTableHeader* usage_table_header) {
LOGV("Creating usage table header: requested_security_level = %s",
RequestedSecurityLevelToString(requested_security_level));
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 CdmResponseType(NO_ERROR);
default:
return MapOEMCryptoResult(result, CREATE_USAGE_TABLE_ERROR,
"CreateUsageTableHeader");
}
}
CdmResponseType CryptoSession::LoadUsageTableHeader(
RequestedSecurityLevel requested_security_level,
const UsageTableHeader& usage_table_header) {
LOGV("Loading usage table header: requested_security_level = %s",
RequestedSecurityLevelToString(requested_security_level));
const OEMCryptoResult result = WithOecWriteLock("LoadUsageTableHeader", [&] {
return 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 CdmResponseType(NO_ERROR);
case OEMCrypto_ERROR_GENERATION_SKEW:
return CRYPTO_ERROR(LOAD_USAGE_HEADER_GENERATION_SKEW, result);
case OEMCrypto_ERROR_SIGNATURE_FAILURE:
return CRYPTO_ERROR(LOAD_USAGE_HEADER_SIGNATURE_FAILURE, result);
case OEMCrypto_ERROR_BAD_MAGIC:
return CRYPTO_ERROR(LOAD_USAGE_HEADER_BAD_MAGIC, result);
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
return CRYPTO_ERROR(SYSTEM_INVALIDATED_ERROR, result);
case OEMCrypto_ERROR_UNKNOWN_FAILURE:
default:
return CRYPTO_ERROR(LOAD_USAGE_HEADER_UNKNOWN_ERROR, result);
}
}
CdmResponseType CryptoSession::ShrinkUsageTableHeader(
RequestedSecurityLevel requested_security_level, uint32_t new_entry_count,
UsageTableHeader* usage_table_header) {
LOGV("Shrinking usage table header: requested_security_level = %s",
RequestedSecurityLevelToString(requested_security_level));
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 CdmResponseType(NO_ERROR);
case OEMCrypto_ERROR_ENTRY_IN_USE:
return CRYPTO_ERROR(SHRINK_USAGE_TABLE_HEADER_ENTRY_IN_USE, result);
default:
return MapOEMCryptoResult(result, SHRINK_USAGE_TABLE_HEADER_UNKNOWN_ERROR,
"ShrinkUsageTableHeader");
}
}
CdmResponseType CryptoSession::CreateUsageEntry(UsageEntryIndex* entry_index) {
LOGV("Creating usage entry: id = %u", oec_session_id_);
RETURN_IF_NULL(entry_index, PARAMETER_NULL);
OEMCryptoResult result;
WithOecWriteLock("CreateUsageEntry", [&] {
result = OEMCrypto_CreateNewUsageEntry(oec_session_id_, entry_index);
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 CdmResponseType(NO_ERROR);
case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES:
return CRYPTO_ERROR(INSUFFICIENT_CRYPTO_RESOURCES, result);
case OEMCrypto_ERROR_SESSION_LOST_STATE:
return CRYPTO_ERROR(SESSION_LOST_STATE_ERROR, result);
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
return CRYPTO_ERROR(SYSTEM_INVALIDATED_ERROR, result);
default:
return CRYPTO_ERROR(CREATE_USAGE_ENTRY_UNKNOWN_ERROR, result);
}
}
CdmResponseType CryptoSession::LoadUsageEntry(UsageEntryIndex entry_index,
const UsageEntry& usage_entry) {
LOGV("Loading usage entry: id = %u", oec_session_id_);
OEMCryptoResult result;
WithOecWriteLock("LoadUsageEntry", [&] {
result = OEMCrypto_LoadUsageEntry(
oec_session_id_, entry_index,
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 CdmResponseType(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 CRYPTO_ERROR(LOAD_USAGE_ENTRY_INVALID_SESSION, result);
case OEMCrypto_ERROR_GENERATION_SKEW:
return CRYPTO_ERROR(LOAD_USAGE_ENTRY_GENERATION_SKEW, result);
case OEMCrypto_ERROR_SIGNATURE_FAILURE:
return CRYPTO_ERROR(LOAD_USAGE_ENTRY_SIGNATURE_FAILURE, result);
default:
return MapOEMCryptoResult(result, LOAD_USAGE_ENTRY_UNKNOWN_ERROR,
"LoadUsageEntry");
}
}
CdmResponseType CryptoSession::UpdateUsageEntry(
UsageTableHeader* usage_table_header, UsageEntry* 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(UsageEntryIndex new_entry_index) {
LOGV("Moving usage entry: id = %u", oec_session_id_);
OEMCryptoResult result;
WithOecWriteLock("MoveUsageEntry", [&] {
result = OEMCrypto_MoveEntry(oec_session_id_, new_entry_index);
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_index);
return CRYPTO_ERROR(MOVE_USAGE_ENTRY_DESTINATION_IN_USE, result);
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_);
RETURN_IF_UNINITIALIZED(false);
const uint32_t flags = WithOecReadLock("GetAnalogOutputCapabilities", [&] {
return 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;
}
OEMCryptoResult CryptoSession::DecryptMultipleSamples(
const std::vector<OEMCrypto_SampleDescription>& samples,
CdmCipherMode cipher_mode, const OEMCrypto_CENCEncryptPatternDesc& pattern,
bool is_any_subsample_protected) {
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,
is_any_subsample_protected);
});
}
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,
is_any_subsample_protected);
if (sts != OEMCrypto_SUCCESS) break;
}
}
return sts;
}
OEMCryptoResult CryptoSession::DecryptSample(
const OEMCrypto_SampleDescription& sample, CdmCipherMode cipher_mode,
const OEMCrypto_CENCEncryptPatternDesc& pattern,
bool is_any_subsample_protected) {
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,
is_any_subsample_protected);
});
}
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,
is_any_subsample_protected);
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,
is_any_subsample_protected);
if (sts != OEMCrypto_SUCCESS) break;
fake_sample.buffers.input_data += length;
AdvanceDestBuffer(&fake_sample.buffers.output_descriptor, length);
if (cipher_mode == kCipherModeCtr) {
wvutil::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,
bool is_any_subsample_protected) {
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,
is_any_subsample_protected);
});
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,
is_any_subsample_protected);
}
}
}
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,
bool is_any_subsample_protected) {
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,
is_any_subsample_protected);
});
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.
wvutil::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;
}
CdmResponseType CryptoSession::SetDebugIgnoreKeyboxCount(uint32_t count) {
OEMCryptoResult status = OEMCrypto_SetDebugIgnoreKeyboxCount(count);
return MapOEMCryptoResult(status, UNKNOWN_ERROR, "SetDebugIgnoreKeyboxCount");
}
okp::SystemFallbackPolicy* CryptoSession::GetOkpFallbackPolicy() {
const auto getter = [&]() -> okp::SystemFallbackPolicy* {
// If not set, then OTA keybox provisioning is not supported or
// not needed.
if (!okp_fallback_policy_l1_) return nullptr;
return okp_fallback_policy_l1_.get();
};
return WithStaticFieldReadLock("GetOkpFallbackPolicy", getter);
}
CdmResponseType CryptoSession::PrepareOtaProvisioningRequest(
bool use_test_key, std::string* request) {
RETURN_IF_NULL(request, PARAMETER_NULL);
RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN);
size_t buffer_length = 0;
OEMCryptoResult status =
WithOecWriteLock("PrepareOtaProvisioningRequest", [&] {
return OEMCrypto_GenerateOTARequest(
oec_session_id_, nullptr, &buffer_length, use_test_key ? 1 : 0);
});
if (status != OEMCrypto_ERROR_SHORT_BUFFER)
return MapOEMCryptoResult(status, UNKNOWN_ERROR,
"PrepareOtaProvisioningRequest");
if (buffer_length == 0) {
LOGE("OTA request size is zero");
return CdmResponseType(UNKNOWN_ERROR);
}
request->resize(buffer_length);
uint8_t* buf = reinterpret_cast<uint8_t*>(&request->front());
status = WithOecWriteLock("PrepareOtaProvisioningRequest", [&] {
return OEMCrypto_GenerateOTARequest(oec_session_id_, buf, &buffer_length,
use_test_key ? 1 : 0);
});
if (OEMCrypto_SUCCESS != status) {
request->clear();
} else if (buffer_length != request->size()) {
request->resize(buffer_length);
}
return MapOEMCryptoResult(status, UNKNOWN_ERROR,
"PrepareOtaProvisioningRequest");
}
CdmResponseType CryptoSession::LoadOtaProvisioning(
bool use_test_key, const std::string& response) {
RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN);
const OEMCryptoResult status = WithOecWriteLock("LoadOtaProvisioning", [&] {
return OEMCrypto_ProcessOTAKeybox(
oec_session_id_, reinterpret_cast<const uint8_t*>(response.data()),
response.size(), use_test_key ? 1 : 0);
});
if (status == OEMCrypto_SUCCESS) {
WithOecWriteLock("LoadOtaProvisioning",
[&] { needs_keybox_provisioning_ = false; });
}
return MapOEMCryptoResult(status, UNKNOWN_ERROR, "LoadOtaProvisioning");
}
template <class Func>
auto CryptoSession::WithStaticFieldWriteLock(const char* tag,
Func body) -> decltype(body()) {
LOGV("Static field write lock: %s", tag);
std::unique_lock<wvutil::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);
wvutil::shared_lock<wvutil::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<wvutil::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);
wvutil::shared_lock<wvutil::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);
wvutil::shared_lock<wvutil::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