Files
android/libwvdrmengine/cdm/core/src/crypto_session.cpp
Alex Dale 1069ae39cc Resize OEMCrypto buffer-based results on success.
[ Merge of http://go/wvgerrit/151191 ]

Within the CDM and OEMCrypto tests, there were a few OEMCrypto function
calls where the final size of the output buffers were not being
resized.  For several of these functions, an initial call is made with
zero-length output buffers, expecting OEMCrypto to return
ERROR_SHORT_BUFFER; followed by a call with buffers at least as large
as specified by OEMCrypto.  However, for some operations, OEMCrypto
makes an estimate on the final size on the first call, specifying the
exact size only after performing the operations.

This is the case for the wrapped key returned by
OEMCrypto_LoadProvisioning().  The provisioning response contains a
padded + encrypted DRM key.  OEMCrypto does not know the actual size
of the key until decrypted, and the actual DRM key might be smaller.

There was a OEMCrypto test for OEMCrypto_BuildInformation() which
was enforcing the wrong behaviour.  This has been updated.

Bug: 230661565
Test: oemcrypto_test
Change-Id: Iad297d56ffbb085894641fdf8698ce5fd18edbf2
2022-05-04 21:32:08 -07:00

3391 lines
124 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 "content_key_session.h"
#include "crypto_key.h"
#include "entitlement_key_session.h"
#include "log.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 "usage_table_header.h"
#include "wv_cdm_constants.h"
// Stringify turns macro arguments into static C strings.
// Example: STRINGIFY(this_argument) -> "this_argument"
#define STRINGIFY(PARAM) #PARAM
#define RETURN_IF_NULL(PARAM, ret_value) \
if ((PARAM) == nullptr) { \
LOGE("Output parameter |" STRINGIFY(PARAM) "| not provided"); \
return ret_value; \
}
#define RETURN_IF_UNINITIALIZED(ret_value) \
if (!IsInitialized()) { \
LOGE("Crypto session is not initialized"); \
return ret_value; \
}
#define RETURN_IF_NOT_OPEN(ret_value) \
if (!open_) { \
LOGE("Crypto session is not open"); \
return ret_value; \
}
#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;
const size_t kAes128BlockSize = 16;
// Constants and utility objects relating to OEM Certificates
constexpr const char* kWidevineSystemIdExtensionOid = "1.3.6.1.4.1.11129.4.1.1";
constexpr int kMaxTerminateCountDown = 5;
const std::string kStringNotAvailable = "NA";
// 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];
// Not a valid system ID. Used as a placeholder for systems without an ID.
// Will not be accepted for DRM provisioning requests or license requests.
constexpr uint32_t kNullSystemId =
static_cast<uint32_t>(std::numeric_limits<int>::max());
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,
CdmResponseType default_status,
const char* crypto_session_method) {
if (result != OEMCrypto_SUCCESS) {
LOGE("Mapping OEMCrypto result: crypto_session_method = %s, result = %d",
crypto_session_method ? crypto_session_method : "N/A",
static_cast<int>(result));
}
switch (result) {
case OEMCrypto_SUCCESS:
return NO_ERROR;
case OEMCrypto_ERROR_NOT_IMPLEMENTED:
return NOT_IMPLEMENTED_ERROR;
case OEMCrypto_ERROR_TOO_MANY_SESSIONS:
return INSUFFICIENT_CRYPTO_RESOURCES;
case OEMCrypto_ERROR_SESSION_LOST_STATE:
return SESSION_LOST_STATE_ERROR;
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
return SYSTEM_INVALIDATED_ERROR;
default:
return default_status;
}
}
void AdvanceDestBuffer(OEMCrypto_DestBufferDesc* dest_buffer, size_t bytes) {
switch (dest_buffer->type) {
case OEMCrypto_BufferType_Clear:
dest_buffer->buffer.clear.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;
}
} // 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<UsageTableHeader> CryptoSession::usage_table_header_l1_;
std::unique_ptr<UsageTableHeader> CryptoSession::usage_table_header_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;
}
void GenerateMacContext(const std::string& input_context,
std::string* deriv_context) {
if (!deriv_context) {
LOGE("Output parameter |deriv_context| not provided");
return;
}
const std::string kSigningKeyLabel = "AUTHENTICATION";
const size_t kSigningKeySizeBits = wvcdm::MAC_KEY_SIZE * 8;
deriv_context->assign(kSigningKeyLabel);
deriv_context->append(1, '\0');
deriv_context->append(input_context);
deriv_context->append(wvutil::EncodeUint32(kSigningKeySizeBits * 2));
}
void GenerateEncryptContext(const std::string& input_context,
std::string* deriv_context) {
if (!deriv_context) {
LOGE("Output parameter |deriv_context| not provided");
return;
}
const std::string kEncryptionKeyLabel = "ENCRYPTION";
const size_t kEncryptionKeySizeBits = wvcdm::CONTENT_KEY_SIZE * 8;
deriv_context->assign(kEncryptionKeyLabel);
deriv_context->append(1, '\0');
deriv_context->append(input_context);
deriv_context->append(wvutil::EncodeUint32(kEncryptionKeySizeBits));
}
OEMCryptoCipherMode ToOEMCryptoCipherMode(CdmCipherMode cipher_mode) {
return cipher_mode == kCipherModeCtr ? OEMCrypto_CipherMode_CENC
: OEMCrypto_CipherMode_CBCS;
}
CryptoSession::CryptoSession(metrics::CryptoMetrics* metrics)
: metrics_(metrics),
system_id_(-1),
open_(false),
pre_provision_token_type_(kClientTokenUninitialized),
update_usage_table_after_close_session_(false),
is_destination_buffer_type_valid_(false),
requested_security_level_(kLevelDefault),
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_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 GET_PROVISIONING_METHOD_ERROR;
}
*token_type = type;
return NO_ERROR;
}
void CryptoSession::Init() {
LOGV("Initializing crypto session");
bool initialized = false;
WithStaticFieldWriteLock("Init", [&] {
session_count_ += 1;
if (!initialized_) {
std::string sandbox_id;
OEMCryptoResult sts;
WithOecWriteLock("Init", [&] {
if (Properties::GetSandboxId(&sandbox_id) && !sandbox_id.empty()) {
sts = OEMCrypto_SetSandbox(
reinterpret_cast<const uint8_t*>(sandbox_id.c_str()),
sandbox_id.length());
metrics_->oemcrypto_set_sandbox_.Record(sandbox_id);
}
M_TIME(sts = OEMCrypto_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_header_l1_.reset();
usage_table_header_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;
}
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_header_l1_.reset();
usage_table_header_l3_.reset();
}
return terminated;
}
void CryptoSession::DisableDelayedTermination() {
LOGV("Disable delayed termination");
WithStaticFieldWriteLock("DisableDelayedTermination",
[&] { termination_counter_ = 0; });
}
bool CryptoSession::SetUpUsageTableHeader(
RequestedSecurityLevel requested_security_level) {
if (usage_table_header_ != 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 (!HasUsageInfoSupport(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: SetUpUsageTableHeader()");
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<UsageTableHeader>& header = security_level == kSecurityLevelL1
? usage_table_header_l1_
: usage_table_header_l3_;
if (!header) {
// 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.
header.reset(new UsageTableHeader());
if (!header->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 UsageTableHeader.
header.reset();
return false;
}
}
usage_table_header_ = header.get();
metrics_->usage_table_header_initial_size_.Record(
usage_table_header_->size());
return true;
}
CdmResponseType CryptoSession::GetTokenFromKeybox(std::string* token) {
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
RETURN_IF_NULL(token, PARAMETER_NULL);
std::string temp_buffer(KEYBOX_KEY_DATA_SIZE, '\0');
size_t buf_size = temp_buffer.size();
uint8_t* buf = reinterpret_cast<uint8_t*>(&temp_buffer[0]);
OEMCryptoResult status;
WithOecReadLock("GetTokenFromKeybox", [&] {
M_TIME(status =
OEMCrypto_GetKeyData(buf, &buf_size, requested_security_level_),
metrics_, oemcrypto_get_key_data_, status,
metrics::Pow2Bucket(buf_size));
});
if (OEMCrypto_SUCCESS == status) {
token->swap(temp_buffer);
}
return MapOEMCryptoResult(status, GET_TOKEN_FROM_KEYBOX_ERROR,
"GetTokenFromKeybox");
}
CdmResponseType CryptoSession::GetTokenFromOemCert(std::string* token) {
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
RETURN_IF_NULL(token, PARAMETER_NULL);
OEMCryptoResult status;
if (!oem_token_.empty()) {
token->assign(oem_token_);
return NO_ERROR;
}
std::string temp_buffer(CERTIFICATE_DATA_SIZE, '\0');
bool retrying = false;
while (true) {
size_t buf_size = temp_buffer.size();
uint8_t* buf = reinterpret_cast<uint8_t*>(&temp_buffer[0]);
WithOecSessionLock("GetTokenFromOemCert", [&] {
status = OEMCrypto_GetOEMPublicCertificate(buf, &buf_size,
requested_security_level_);
});
metrics_->oemcrypto_get_oem_public_certificate_.Increment(status);
if (OEMCrypto_SUCCESS == status) {
temp_buffer.resize(buf_size);
oem_token_.assign(temp_buffer);
token->assign(temp_buffer);
return NO_ERROR;
}
if (status == OEMCrypto_ERROR_SHORT_BUFFER && !retrying) {
temp_buffer.resize(buf_size);
retrying = true;
continue;
}
return MapOEMCryptoResult(status, GET_TOKEN_FROM_OEM_CERT_ERROR,
"GetTokenFromOemCert");
}
}
CdmResponseType CryptoSession::GetProvisioningToken(
std::string* token, std::string* additional_token) {
if (token == nullptr || additional_token == nullptr) {
metrics_->crypto_session_get_token_.Increment(PARAMETER_NULL);
RETURN_IF_NULL(token, PARAMETER_NULL);
RETURN_IF_NULL(additional_token, PARAMETER_NULL);
}
if (!IsInitialized()) {
metrics_->crypto_session_get_token_.Increment(
CRYPTO_SESSION_NOT_INITIALIZED);
return CRYPTO_SESSION_NOT_INITIALIZED;
}
CdmResponseType status = UNKNOWN_CLIENT_TOKEN_TYPE;
if (pre_provision_token_type_ == kClientTokenKeybox) {
status = GetTokenFromKeybox(token);
} else if (pre_provision_token_type_ == kClientTokenOemCert) {
status = GetTokenFromOemCert(token);
} else if (pre_provision_token_type_ == kClientTokenBootCertChain) {
status = GetBootCertificateChain(token, additional_token);
}
metrics_->crypto_session_get_token_.Increment(status);
return status;
}
CdmSecurityLevel CryptoSession::GetSecurityLevel() {
LOGV("Getting security level");
RETURN_IF_NOT_OPEN(kSecurityLevelUninitialized);
return GetSecurityLevel(requested_security_level_);
}
CdmSecurityLevel CryptoSession::GetSecurityLevel(
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 NO_ERROR;
}
device_id->clear();
if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED &&
pre_provision_token_type_ == kClientTokenOemCert) {
return GetTokenFromOemCert(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 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 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::GetSystemId(uint32_t* system_id) {
RETURN_IF_NULL(system_id, false);
RETURN_IF_UNINITIALIZED(false);
RETURN_IF_NOT_OPEN(false);
*system_id = system_id_;
return true;
}
// This method gets the system id from the keybox key data.
// This method assumes that OEMCrypto has been initialized before making this
// call.
CdmResponseType CryptoSession::GetSystemIdInternal(uint32_t* system_id) {
RETURN_IF_NULL(system_id, PARAMETER_NULL);
if (pre_provision_token_type_ == kClientTokenKeybox) {
const bool use_null_system_id = WithStaticFieldReadLock(
"GetSystemIdInternal() use_null_system_id", [&] {
// Devices with an invalid L1 keybox which support OTA keybox
// provisioning require a placeholder system ID while waiting for
// keybox.
if (requested_security_level_ != kLevelDefault) return false;
return needs_keybox_provisioning_;
});
if (use_null_system_id) {
LOGD("Using null system ID");
*system_id = kNullSystemId;
return NO_ERROR;
}
std::string token;
const CdmResponseType status = GetTokenFromKeybox(&token);
if (status != NO_ERROR) return status;
if (token.size() < 2 * sizeof(uint32_t)) {
LOGE("Keybox token size too small: token_size = %zu", token.size());
return KEYBOX_TOKEN_TOO_SHORT;
}
// Decode 32-bit int encoded as network-byte-order byte array starting at
// index 4.
const uint32_t* id = reinterpret_cast<const uint32_t*>(&token[4]);
*system_id = ntohl(*id);
return NO_ERROR;
}
if (pre_provision_token_type_ == kClientTokenOemCert) {
// Get the OEM Cert
std::string oem_cert;
CdmResponseType status = GetTokenFromOemCert(&oem_cert);
if (status != NO_ERROR) return status;
if (!ExtractSystemIdFromOemCert(oem_cert, system_id))
return EXTRACT_SYSTEM_ID_FROM_OEM_CERT_ERROR;
return NO_ERROR;
}
if (pre_provision_token_type_ == kClientTokenDrmCert) {
// TODO(blueeyes): Support loading the system id from a pre-provisioned
// Drm certificate.
return NO_ERROR;
}
if (pre_provision_token_type_ == kClientTokenBootCertChain) {
// A system id can not be inferred from BCC. If the provisioning process has
// come to the second stage, we may read system id from the stored OEM cert.
return NO_ERROR;
}
LOGE("Unsupported pre-provision token type: %d",
static_cast<int>(pre_provision_token_type_));
return UNKNOWN_CLIENT_TOKEN_TYPE;
}
bool CryptoSession::ExtractSystemIdFromOemCert(const std::string& oem_cert,
uint32_t* system_id) {
return ExtractExtensionValueFromCertificate(
oem_cert, kWidevineSystemIdExtensionOid, /* cert_index */ 1, system_id);
}
CdmResponseType CryptoSession::GetProvisioningId(std::string* provisioning_id) {
RETURN_IF_NULL(provisioning_id, PARAMETER_NULL);
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
if (pre_provision_token_type_ == kClientTokenOemCert) {
// OEM Cert devices have no provisioning-unique ID embedded in them, so we
// synthesize one by using the External Device-Unique ID and inverting all
// the bits.
CdmResponseType status = GetExternalDeviceUniqueId(provisioning_id);
if (status != NO_ERROR) return status;
for (size_t i = 0; i < provisioning_id->size(); ++i) {
char value = (*provisioning_id)[i];
(*provisioning_id)[i] = ~value;
}
return NO_ERROR;
}
if (pre_provision_token_type_ == kClientTokenKeybox) {
std::string token;
CdmResponseType status = GetTokenFromKeybox(&token);
if (status != NO_ERROR) return status;
if (token.size() < 24) {
LOGE("Keybox token size too small: %zu", token.size());
return KEYBOX_TOKEN_TOO_SHORT;
}
provisioning_id->assign(reinterpret_cast<char*>(&token[8]), 16);
return NO_ERROR;
}
LOGE("Unsupported pre-provision token type: %d",
static_cast<int>(pre_provision_token_type_));
return UNKNOWN_CLIENT_TOKEN_TYPE;
}
uint8_t CryptoSession::GetSecurityPatchLevel() {
uint8_t patch;
WithOecReadLock("GetSecurityPatchLevel", [&] {
patch = OEMCrypto_Security_Patch_Level(requested_security_level_);
});
metrics_->oemcrypto_security_patch_level_.Record(patch);
return patch;
}
CdmResponseType CryptoSession::Open(
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 NO_ERROR;
if (!SetUpUsageTableHeader(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;
// Get System ID and save it.
result = GetSystemIdInternal(&system_id_);
if (result == NO_ERROR) {
metrics_->crypto_session_system_id_.Record(system_id_);
} else {
LOGE("Failed to fetch system ID");
metrics_->crypto_session_system_id_.SetError(result);
return result;
}
// Set up request ID
uint64_t request_id_base;
OEMCryptoResult random_sts;
WithOecReadLock("Open() calling OEMCrypto_GetRandom", [&] {
random_sts = OEMCrypto_GetRandom(
reinterpret_cast<uint8_t*>(&request_id_base), sizeof(request_id_base));
});
metrics_->oemcrypto_get_random_.Increment(random_sts);
uint64_t request_id_index =
request_id_index_source_.fetch_add(1, std::memory_order_relaxed);
request_id_ = 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(oec_session_id_, metrics_));
});
if (!GetApiVersion(&api_version_)) {
LOGE("Failed to get API version");
return USAGE_SUPPORT_GET_API_FAILED;
}
return 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_info_support_ = kBooleanUnset;
oem_token_.clear();
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_header_ = 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) {
LOGV("Preparing and signing license request: id = %u", oec_session_id_);
RETURN_IF_NULL(signature, PARAMETER_NULL);
RETURN_IF_NULL(core_message, PARAMETER_NULL);
OEMCryptoResult sts;
size_t signature_length = 0;
size_t core_message_length = 0;
*core_message = "";
std::string combined_message = *core_message + message;
// First call is intended to determine the required size of the
// output buffers.
WithOecSessionLock("PrepareAndSignLicenseRequest", [&] {
M_TIME(sts = OEMCrypto_PrepAndSignLicenseRequest(
oec_session_id_,
reinterpret_cast<uint8_t*>(
const_cast<char*>(combined_message.data())),
combined_message.size(), &core_message_length, nullptr,
&signature_length),
metrics_, oemcrypto_prep_and_sign_license_request_, sts);
});
if (OEMCrypto_ERROR_SHORT_BUFFER != sts) {
// 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 NEED_PROVISIONING;
}
return MapOEMCryptoResult(sts, GENERATE_SIGNATURE_ERROR,
"PrepareAndSignLicenseRequest");
}
// Resize.
core_message->resize(core_message_length);
signature->resize(signature_length);
combined_message = *core_message + message;
WithOecSessionLock("PrepareAndSignLicenseRequest", [&] {
M_TIME(sts = OEMCrypto_PrepAndSignLicenseRequest(
oec_session_id_,
reinterpret_cast<uint8_t*>(
const_cast<char*>(combined_message.data())),
combined_message.size(), &core_message_length,
reinterpret_cast<uint8_t*>(const_cast<char*>(signature->data())),
&signature_length),
metrics_, oemcrypto_prep_and_sign_license_request_, sts);
});
if (OEMCrypto_SUCCESS == sts) {
signature->resize(signature_length);
*core_message = std::move(combined_message);
// Truncate combined message to only contain the core message.
core_message->resize(core_message_length);
return NO_ERROR;
}
// 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 NEED_PROVISIONING;
}
return MapOEMCryptoResult(sts, GENERATE_SIGNATURE_ERROR,
"PrepareAndSignLicenseRequest");
}
CdmResponseType CryptoSession::UseSecondaryKey(bool dual_key) {
#ifdef HAS_DUAL_KEY
OEMCryptoResult sts;
WithOecSessionLock("UseSecondaryKey", [&] {
sts = OEMCrypto_UseSecondaryKey(oec_session_id_, dual_key);
});
return MapOEMCryptoResult(sts, LOAD_KEY_ERROR, "UseSecondaryKey");
#else
return NO_ERROR;
#endif
}
CdmResponseType CryptoSession::LoadKeys(
const std::string& message, const std::string& signature,
const std::string& mac_key_iv, const std::string& mac_key,
const std::vector<CryptoKey>& keys,
const std::string& provider_session_token,
const std::string& srm_requirement, CdmLicenseKeyType key_type) {
LOGV("Loading keys: id = %u", oec_session_id_);
OEMCryptoResult sts;
WithOecSessionLock("LoadKeys", [&] {
if (key_type == kLicenseKeyTypeEntitlement &&
key_session_->Type() != KeySession::kEntitlement) {
key_session_.reset(new EntitlementKeySession(oec_session_id_, metrics_));
}
LOGV("Loading key: id = %u", oec_session_id_);
sts = key_session_->LoadKeys(message, signature, mac_key_iv, mac_key, keys,
provider_session_token, srm_requirement);
});
if (sts != OEMCrypto_SUCCESS) {
LOGE("OEMCrypto_LoadKeys failed: status = %d", static_cast<int>(sts));
}
switch (sts) {
case OEMCrypto_SUCCESS:
if (!provider_session_token.empty())
update_usage_table_after_close_session_ = true;
return KEY_ADDED;
case OEMCrypto_ERROR_TOO_MANY_KEYS:
return INSUFFICIENT_CRYPTO_RESOURCES;
case OEMCrypto_ERROR_USAGE_TABLE_UNRECOVERABLE:
// Handle vendor specific error
return NEED_PROVISIONING;
default:
break;
}
return MapOEMCryptoResult(sts, LOAD_KEY_ERROR, "LoadKeys");
}
CdmResponseType CryptoSession::LoadLicense(const std::string& signed_message,
const std::string& core_message,
const std::string& signature,
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(oec_session_id_, metrics_));
}
M_TIME(sts = OEMCrypto_LoadLicense(
oec_session_id_,
reinterpret_cast<const uint8_t*>(combined_message.data()),
combined_message.size(), core_message.size(),
reinterpret_cast<const uint8_t*>(signature.data()),
signature.size()),
metrics_, oemcrypto_load_license_, sts);
});
switch (sts) {
case OEMCrypto_SUCCESS:
return KEY_ADDED;
case OEMCrypto_ERROR_BUFFER_TOO_LARGE:
LOGE("LoadLicense buffer too large: size = %zu", combined_message.size());
return LOAD_LICENSE_ERROR;
case OEMCrypto_ERROR_TOO_MANY_KEYS:
LOGE("Too many keys in license");
return INSUFFICIENT_CRYPTO_RESOURCES;
default:
break;
}
return MapOEMCryptoResult(sts, LOAD_LICENSE_ERROR, "LoadLicense");
}
CdmResponseType CryptoSession::PrepareAndSignRenewalRequest(
const std::string& message, std::string* core_message,
std::string* signature) {
LOGV("Preparing and signing renewal request: id = %u", oec_session_id_);
if (signature == nullptr) {
LOGE("Output parameter |signature| not provided");
return PARAMETER_NULL;
}
if (core_message == nullptr) {
LOGE("Output parameter |core_message| not provided");
return PARAMETER_NULL;
}
OEMCryptoResult sts;
size_t signature_length = 0;
size_t core_message_length = 0;
*core_message = "";
std::string combined_message = *core_message + message;
// First call is intended to determine the required size of the
// output buffers.
WithOecSessionLock("PrepareAndSignRenewalRequest", [&] {
M_TIME(sts = OEMCrypto_PrepAndSignRenewalRequest(
oec_session_id_,
reinterpret_cast<uint8_t*>(
const_cast<char*>(combined_message.data())),
combined_message.size(), &core_message_length, nullptr,
&signature_length),
metrics_, oemcrypto_prep_and_sign_renewal_request_, sts);
});
if (OEMCrypto_ERROR_SHORT_BUFFER != sts) {
return MapOEMCryptoResult(sts, GENERATE_SIGNATURE_ERROR,
"PrepareAndSignRenewalRequest");
}
// Resize.
core_message->resize(core_message_length);
signature->resize(signature_length);
combined_message = *core_message + message;
WithOecSessionLock("PrepareAndSignRenewalRequest", [&] {
M_TIME(sts = OEMCrypto_PrepAndSignRenewalRequest(
oec_session_id_,
reinterpret_cast<uint8_t*>(
const_cast<char*>(combined_message.data())),
combined_message.size(), &core_message_length,
reinterpret_cast<uint8_t*>(const_cast<char*>(signature->data())),
&signature_length),
metrics_, oemcrypto_prep_and_sign_renewal_request_, sts);
});
if (OEMCrypto_SUCCESS == sts) {
signature->resize(signature_length);
*core_message = std::move(combined_message);
// Truncate combined message to only contain the core message.
core_message->resize(core_message_length);
return NO_ERROR;
}
return MapOEMCryptoResult(sts, GENERATE_SIGNATURE_ERROR,
"PrepareAndSignRenewalRequest");
}
CdmResponseType CryptoSession::RefreshKeys(
const std::string& message, const std::string& signature,
const std::vector<CryptoKey>& key_array) {
const uint8_t* msg = reinterpret_cast<const uint8_t*>(message.data());
std::vector<OEMCrypto_KeyRefreshObject> load_key_array(key_array.size());
for (size_t i = 0; i < key_array.size(); ++i) {
const CryptoKey* ki = &key_array[i];
OEMCrypto_KeyRefreshObject* ko = &load_key_array[i];
ko->key_id = GetSubstring(message, ki->key_id());
bool has_key_control = ki->HasKeyControl();
ko->key_control_iv =
GetSubstring(message, ki->key_control_iv(), !has_key_control);
ko->key_control =
GetSubstring(message, ki->key_control(), !has_key_control);
}
LOGV("Refreshing keys: id = %u", oec_session_id_);
OEMCryptoResult refresh_sts;
WithOecSessionLock("RefreshKeys", [&] {
M_TIME(refresh_sts = OEMCrypto_RefreshKeys(
oec_session_id_, msg, message.size(),
reinterpret_cast<const uint8_t*>(signature.data()),
signature.size(), key_array.size(), &load_key_array[0]),
metrics_, oemcrypto_refresh_keys_, refresh_sts);
});
if (refresh_sts == OEMCrypto_SUCCESS) return KEY_ADDED;
return MapOEMCryptoResult(refresh_sts, REFRESH_KEYS_ERROR, "RefreshKeys");
}
CdmResponseType CryptoSession::LoadRenewal(const std::string& signed_message,
const std::string& core_message,
const std::string& signature) {
LOGV("Loading license renewal: id = %u", oec_session_id_);
const std::string combined_message = core_message + signed_message;
OEMCryptoResult sts;
WithOecSessionLock("LoadRenewal", [&] {
M_TIME(sts = OEMCrypto_LoadRenewal(
oec_session_id_,
reinterpret_cast<const uint8_t*>(combined_message.data()),
combined_message.size(), core_message.size(),
reinterpret_cast<const uint8_t*>(signature.data()),
signature.size()),
metrics_, oemcrypto_load_renewal_, sts);
});
if (sts == OEMCrypto_SUCCESS) {
return KEY_ADDED;
}
if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE) {
LOGE("Buffer too large: size = %zu", combined_message.size());
return LOAD_RENEWAL_ERROR;
}
return MapOEMCryptoResult(sts, LOAD_RENEWAL_ERROR, "LoadRenewal");
}
CdmResponseType CryptoSession::PrepareAndSignProvisioningRequest(
const std::string& message, std::string* core_message,
std::string* signature) {
LOGV("Preparing and signing provisioning request: id = %u", oec_session_id_);
if (signature == nullptr) {
LOGE("Output parameter |signature| not provided");
return PARAMETER_NULL;
}
if (core_message == nullptr) {
LOGE("Output parameter |core_message| not provided");
return PARAMETER_NULL;
}
if (pre_provision_token_type_ == kClientTokenKeybox) {
const CdmResponseType status = GenerateDerivedKeys(message);
if (status != NO_ERROR) return status;
} else if (pre_provision_token_type_ == kClientTokenOemCert) {
const OEMCryptoResult status = OEMCrypto_LoadOEMPrivateKey(oec_session_id_);
if (status != OEMCrypto_SUCCESS) {
return MapOEMCryptoResult(status, GET_TOKEN_FROM_OEM_CERT_ERROR,
"PrepareAndSignProvisioningRequest");
}
} else {
LOGE("Unknown method %d", pre_provision_token_type_);
return UNKNOWN_CLIENT_TOKEN_TYPE;
}
OEMCryptoResult sts;
size_t signature_length = 0;
size_t core_message_length = 0;
*core_message = "";
std::string combined_message = *core_message + message;
// First call is intended to determine the required size of the
// output buffers.
WithOecSessionLock("PrepareAndSignProvisioningRequest", [&] {
M_TIME(sts = OEMCrypto_PrepAndSignProvisioningRequest(
oec_session_id_,
reinterpret_cast<uint8_t*>(
const_cast<char*>(combined_message.data())),
combined_message.size(), &core_message_length, nullptr,
&signature_length),
metrics_, oemcrypto_prep_and_sign_provisioning_request_, sts);
});
if (OEMCrypto_ERROR_SHORT_BUFFER != sts) {
return MapOEMCryptoResult(sts, GENERATE_SIGNATURE_ERROR,
"PrepareAndSignProvisioningRequest");
}
// Resize.
core_message->resize(core_message_length);
signature->resize(signature_length);
combined_message = *core_message + message;
WithOecSessionLock("PrepareAndSignProvisioningRequest", [&] {
M_TIME(sts = OEMCrypto_PrepAndSignProvisioningRequest(
oec_session_id_,
reinterpret_cast<uint8_t*>(
const_cast<char*>(combined_message.data())),
combined_message.size(), &core_message_length,
reinterpret_cast<uint8_t*>(const_cast<char*>(signature->data())),
&signature_length),
metrics_, oemcrypto_prep_and_sign_provisioning_request_, sts);
});
if (OEMCrypto_SUCCESS == sts) {
signature->resize(signature_length);
*core_message = std::move(combined_message);
// Truncate combined message to only contain the core message.
core_message->resize(core_message_length);
return NO_ERROR;
}
return MapOEMCryptoResult(sts, GENERATE_SIGNATURE_ERROR,
"PrepareAndSignProvisioningRequest");
}
CdmResponseType CryptoSession::LoadEntitledContentKeys(
const std::vector<CryptoKey>& key_array) {
const OEMCryptoResult sts = WithOecSessionLock(
"LoadEntitledContentKeys",
[&] { return key_session_->LoadEntitledContentKeys(key_array); });
switch (sts) {
case OEMCrypto_SUCCESS:
return KEY_ADDED;
case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES:
return INSUFFICIENT_CRYPTO_RESOURCES;
case OEMCrypto_ERROR_INVALID_CONTEXT:
return NOT_AN_ENTITLEMENT_SESSION;
case OEMCrypto_KEY_NOT_ENTITLED:
return NO_MATCHING_ENTITLEMENT_KEY;
case OEMCrypto_ERROR_SESSION_LOST_STATE:
return SESSION_LOST_STATE_ERROR;
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
return SYSTEM_INVALIDATED_ERROR;
default:
return LOAD_ENTITLED_CONTENT_KEYS_ERROR;
}
}
CdmResponseType CryptoSession::LoadCertificatePrivateKey(
const 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_NULL(bcc, PARAMETER_NULL);
RETURN_IF_NULL(additional_signature, PARAMETER_NULL);
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
LOGV("GetBootCertificateChain");
if (pre_provision_token_type_ != kClientTokenBootCertChain) {
return PROVISIONING_TYPE_IS_NOT_BOOT_CERTIFICATE_CHAIN_ERROR;
}
size_t bcc_length = 0;
size_t additional_signature_length = 0;
OEMCryptoResult sts;
WithOecReadLock("GetBootCertificateChain Attempt 1", [&] {
sts = 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);
WithOecReadLock("GetBootCertificateChain Attempt 2", [&] {
sts = OEMCrypto_GetBootCertificateChain(
reinterpret_cast<uint8_t*>(&bcc->front()), &bcc_length,
reinterpret_cast<uint8_t*>(&additional_signature->front()),
&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 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_, reinterpret_cast<uint8_t*>(&public_key->front()),
&public_key_length,
reinterpret_cast<uint8_t*>(&public_key_signature->front()),
&public_key_signature_length,
reinterpret_cast<uint8_t*>(&wrapped_private_key->front()),
&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));
return GENERATE_CERTIFICATE_KEY_PAIR_UNKNOWN_TYPE_ERROR;
}
return 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);
});
switch (sts) {
// SelectKey errors.
case OEMCrypto_SUCCESS:
return NO_ERROR;
case OEMCrypto_ERROR_KEY_EXPIRED:
return NEED_KEY;
case OEMCrypto_ERROR_INVALID_SESSION:
return INVALID_SESSION_1;
case OEMCrypto_ERROR_NO_DEVICE_KEY:
return NO_DEVICE_KEY_1;
case OEMCrypto_ERROR_NO_CONTENT_KEY:
return NO_CONTENT_KEY_2;
case OEMCrypto_ERROR_CONTROL_INVALID:
case OEMCrypto_ERROR_KEYBOX_INVALID:
return UNKNOWN_SELECT_KEY_ERROR_2;
case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES:
return INSUFFICIENT_CRYPTO_RESOURCES;
case OEMCrypto_ERROR_UNKNOWN_FAILURE:
return UNKNOWN_SELECT_KEY_ERROR_1;
case OEMCrypto_ERROR_ANALOG_OUTPUT:
return ANALOG_OUTPUT_ERROR;
case OEMCrypto_ERROR_INSUFFICIENT_HDCP:
return INSUFFICIENT_OUTPUT_PROTECTION;
// 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 NOT_AN_ENTITLEMENT_SESSION;
case OEMCrypto_KEY_NOT_ENTITLED:
return NO_MATCHING_ENTITLEMENT_KEY;
// Obsolete errors.
case OEMCrypto_KEY_NOT_LOADED:
return NO_CONTENT_KEY_3;
// Catch all else.
default:
return MapOEMCryptoResult(sts, UNKNOWN_SELECT_KEY_ERROR_2, "SelectKey");
}
}
CdmResponseType CryptoSession::GenerateDerivedKeys(const std::string& message) {
OEMCryptoResult sts;
WithOecSessionLock("GenerateDerivedKeys without session_key",
[&] { sts = key_session_->GenerateDerivedKeys(message); });
return MapOEMCryptoResult(sts, GENERATE_DERIVED_KEYS_ERROR_2,
"GenerateDerivedKeys");
}
CdmResponseType CryptoSession::GenerateDerivedKeys(
const std::string& message, const std::string& session_key) {
OEMCryptoResult sts;
WithOecSessionLock("GenerateDerivedKeys with session_key", [&] {
sts = key_session_->GenerateDerivedKeys(message, session_key);
});
return MapOEMCryptoResult(sts, GENERATE_DERIVED_KEYS_ERROR,
"GenerateDerivedKeys");
}
CdmResponseType CryptoSession::GenerateRsaSignature(const std::string& message,
std::string* signature) {
LOGV("Generating RSA signature: id = %u", oec_session_id_);
RETURN_IF_NULL(signature, PARAMETER_NULL);
OEMCryptoResult sts;
signature->resize(kRsaSignatureLength);
size_t length = signature->size();
// At most two attempts.
// The first attempt may fail due to buffer too short
for (int i = 0; i < 2; ++i) {
WithOecSessionLock("GenerateRsaSignature", [&] {
M_TIME(
sts = OEMCrypto_GenerateRSASignature(
oec_session_id_, reinterpret_cast<const uint8_t*>(message.data()),
message.size(),
reinterpret_cast<uint8_t*>(const_cast<char*>(signature->data())),
&length, kSign_RSASSA_PSS),
metrics_, oemcrypto_generate_rsa_signature_, sts,
metrics::Pow2Bucket(length));
});
if (OEMCrypto_SUCCESS == sts) {
// Trim signature buffer and done
signature->resize(length);
return NO_ERROR;
}
if (OEMCrypto_ERROR_SHORT_BUFFER != sts) {
break;
}
// Retry with proper-sized signature buffer
signature->resize(length);
}
return MapOEMCryptoResult(sts, RSA_SIGNATURE_GENERATION_ERROR,
"OEMCrypto_GenerateRSASignature");
}
size_t CryptoSession::GetMaxSubsampleRegionSize() {
// If we haven't cached the answer yet, fetch it from OEMCrypto.
if (max_subsample_region_size_ == 0) {
uint32_t tier = 0;
if (GetResourceRatingTier(&tier)) {
// Subtract RESOURCE_RATING_TIER_MIN to get a 0-based index into the
// table.
const uint32_t index = tier - RESOURCE_RATING_TIER_MIN;
if (index < 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 UNKNOWN_ERROR;
}
OEMCryptoBufferType output_descriptor_type =
params.is_secure ? destination_buffer_type_ : OEMCrypto_BufferType_Clear;
if (params.is_secure &&
output_descriptor_type == OEMCrypto_BufferType_Clear) {
return SECURE_BUFFER_REQUIRED;
}
if (params.samples.size() == 0) return CANNOT_DECRYPT_ZERO_SAMPLES;
if (std::any_of(std::begin(params.samples), std::end(params.samples),
[](const CdmDecryptionSample& sample) -> bool {
return sample.subsamples.size() == 0;
})) {
return CANNOT_DECRYPT_ZERO_SUBSAMPLES;
}
// Convert all the sample and subsample definitions to OEMCrypto structs.
// This code also caches whether any of the data is protected, to save later
// code the trouble of iterating over all the subsamples to check.
bool is_any_sample_protected = false;
std::vector<OEMCrypto_SampleDescription> oec_samples;
oec_samples.reserve(params.samples.size());
std::vector<std::vector<OEMCrypto_SubSampleDescription>>
oec_subsample_vectors;
oec_subsample_vectors.reserve(params.samples.size());
for (const CdmDecryptionSample& sample : params.samples) {
oec_samples.emplace_back();
OEMCrypto_SampleDescription& oec_sample = oec_samples.back();
// Set up the sample's input buffer
oec_sample.buffers.input_data = sample.encrypt_buffer;
oec_sample.buffers.input_data_length = sample.encrypt_buffer_length;
// Set up the sample's output buffer
OEMCrypto_DestBufferDesc& output_descriptor =
oec_sample.buffers.output_descriptor;
output_descriptor.type = output_descriptor_type;
switch (output_descriptor.type) {
case OEMCrypto_BufferType_Clear:
output_descriptor.buffer.clear.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 SAMPLE_AND_SUBSAMPLE_SIZE_MISMATCH;
// Set up the sample's IV
if (is_any_subsample_protected) {
if (sizeof(oec_sample.iv) != sample.iv.size()) return INVALID_IV_SIZE;
memcpy(oec_sample.iv, sample.iv.data(), sizeof(oec_sample.iv));
} else {
memset(oec_sample.iv, 0, sizeof(oec_sample.iv));
}
// Attach the subsamples to the sample description
oec_sample.subsamples = oec_subsamples.data();
oec_sample.subsamples_length = oec_subsamples.size();
}
// Convert the pattern descriptor
OEMCrypto_CENCEncryptPatternDesc oec_pattern{params.pattern.encrypt_blocks,
params.pattern.skip_blocks};
// TODO(b/146581957): Remove this workaround once OEMCrypto treats (0,0) as
// 'cbcs' instead of 'cbc1'.
if (params.cipher_mode == kCipherModeCbc && oec_pattern.encrypt == 0 &&
oec_pattern.skip == 0) {
// (10, 0) is the preferred pattern for decrypting every block in 'cbcs'
oec_pattern.encrypt = 10;
}
// Check if a key needs to be selected
if (is_any_sample_protected) {
CdmResponseType result = SelectKey(params.key_id, params.cipher_mode);
if (result != NO_ERROR) return result;
}
// Perform decrypt
const OEMCryptoResult sts =
DecryptMultipleSamples(oec_samples, params.cipher_mode, oec_pattern);
if (sts != OEMCrypto_SUCCESS && last_decrypt_error_ != sts) {
// Decrypt errors and warnings are only logged when the error code
// changes. This is in anticipation that if an error code is
// returned, then the same error code is likely to be returned in
// the next call. The calling application may make several more
// decrypt requests before the error is handled by the app.
last_decrypt_error_ = sts;
if (sts == OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION) {
LOGW(
"OEMCrypto_DecryptCENC is warning of mixed HDCP output protection: "
"oec_session_id = %u",
oec_session_id_);
} else {
LOGE("OEMCrypto_DecryptCENC failed: status = %d", static_cast<int>(sts));
}
}
switch (sts) {
case OEMCrypto_SUCCESS:
case OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION:
return NO_ERROR;
case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES:
return INSUFFICIENT_CRYPTO_RESOURCES;
case OEMCrypto_ERROR_KEY_EXPIRED:
return NEED_KEY;
case OEMCrypto_ERROR_INVALID_SESSION:
return INVALID_SESSION_2;
case OEMCrypto_ERROR_DECRYPT_FAILED:
case OEMCrypto_ERROR_UNKNOWN_FAILURE:
return DECRYPT_ERROR;
case OEMCrypto_ERROR_INSUFFICIENT_HDCP:
return INSUFFICIENT_OUTPUT_PROTECTION;
case OEMCrypto_ERROR_ANALOG_OUTPUT:
return ANALOG_OUTPUT_ERROR;
case OEMCrypto_ERROR_OUTPUT_TOO_LARGE:
return OUTPUT_TOO_LARGE_ERROR;
case OEMCrypto_ERROR_SESSION_LOST_STATE:
return SESSION_LOST_STATE_ERROR;
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
return SYSTEM_INVALIDATED_ERROR;
default:
return UNKNOWN_ERROR;
}
}
bool CryptoSession::HasUsageInfoSupport(bool* has_support) {
RETURN_IF_NOT_OPEN(false);
RETURN_IF_NULL(has_support, false);
return WithOecReadLock("HasUsageInfoSupport", [&] {
// Use cached value if set.
if (has_usage_info_support_ != kBooleanUnset) {
*has_support = (has_usage_info_support_ == kBooleanTrue);
return true;
}
if (!HasUsageInfoSupportInternal(requested_security_level_, has_support)) {
return false;
}
// Cache result if successful.
has_usage_info_support_ = (*has_support ? kBooleanTrue : kBooleanFalse);
return true;
});
}
bool CryptoSession::HasUsageInfoSupport(
RequestedSecurityLevel requested_security_level, bool* has_support) {
RETURN_IF_UNINITIALIZED(false);
RETURN_IF_NULL(has_support, false);
return WithOecReadLock("HasUsageInfoSupport", [&] {
return HasUsageInfoSupportInternal(requested_security_level, has_support);
});
}
bool CryptoSession::HasUsageInfoSupportInternal(
RequestedSecurityLevel requested_security_level, bool* has_support) {
LOGV("requested_security_level = %s",
RequestedSecurityLevelToString(requested_security_level));
*has_support = WithOecReadLock("HasUsageInfoSupport", [&] {
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 NO_ERROR;
case OEMCrypto_ERROR_INVALID_CONTEXT:
return KEY_CANCELED;
case OEMCrypto_ERROR_SESSION_LOST_STATE:
return SESSION_LOST_STATE_ERROR;
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
return SYSTEM_INVALIDATED_ERROR;
default:
return DEACTIVATE_USAGE_ENTRY_ERROR;
}
}
CdmResponseType CryptoSession::GenerateUsageReport(
const std::string& provider_session_token, std::string* usage_report,
UsageDurationStatus* usage_duration_status, int64_t* seconds_since_started,
int64_t* seconds_since_last_played) {
LOGV("Generating usage report: id = %u", oec_session_id_);
RETURN_IF_NULL(usage_report, PARAMETER_NULL);
uint8_t* pst = reinterpret_cast<uint8_t*>(
const_cast<char*>(provider_session_token.data()));
size_t usage_length = 0;
OEMCryptoResult status;
WithOecWriteLock("GenerateUsageReport Attempt 1", [&] {
status = OEMCrypto_ReportUsage(oec_session_id_, pst,
provider_session_token.length(), nullptr,
&usage_length);
});
metrics_->oemcrypto_report_usage_.Increment(status);
if (status != OEMCrypto_SUCCESS && status != OEMCrypto_ERROR_SHORT_BUFFER) {
return MapOEMCryptoResult(status, GENERATE_USAGE_REPORT_ERROR,
"GenerateUsageReport");
}
std::vector<uint8_t> buffer(usage_length);
WithOecWriteLock("GenerateUsageReport Attempt 2", [&] {
status = OEMCrypto_ReportUsage(oec_session_id_, pst,
provider_session_token.length(), &buffer[0],
&usage_length);
});
metrics_->oemcrypto_report_usage_.Increment(status);
if (status != OEMCrypto_SUCCESS) {
return MapOEMCryptoResult(status, GENERATE_USAGE_REPORT_ERROR,
"OEMCrypto_ReportUsage");
}
if (usage_length != buffer.size()) {
buffer.resize(usage_length);
}
(*usage_report) =
std::string(reinterpret_cast<const char*>(&buffer[0]), buffer.size());
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 NO_ERROR; // usage report available but no duration information
}
if (kUnused == pst_report.status()) {
*usage_duration_status = kUsageDurationPlaybackNotBegun;
return NO_ERROR;
}
LOGV("OEMCrypto_PST_Report.status: %d\n",
static_cast<int>(pst_report.status()));
LOGV("OEMCrypto_PST_Report.clock_security_level: %d\n",
static_cast<int>(pst_report.clock_security_level()));
LOGV("OEMCrypto_PST_Report.pst_length: %d\n",
static_cast<int>(pst_report.pst_length()));
LOGV("OEMCrypto_PST_Report.padding: %d\n",
static_cast<int>(pst_report.padding()));
LOGV("OEMCrypto_PST_Report.seconds_since_license_received: %" 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 NO_ERROR;
}
// Before OEMCrypto v13, When usage report state is inactive, we have to
// deduce whether the license was ever used.
if (kInactive == pst_report.status() &&
(0 > pst_report.seconds_since_first_decrypt() ||
pst_report.seconds_since_license_received() <
pst_report.seconds_since_first_decrypt())) {
*usage_duration_status = kUsageDurationPlaybackNotBegun;
return NO_ERROR;
}
*usage_duration_status = kUsageDurationsValid;
*seconds_since_started = pst_report.seconds_since_first_decrypt();
*seconds_since_last_played = pst_report.seconds_since_last_decrypt();
return NO_ERROR;
}
bool CryptoSession::IsAntiRollbackHwPresent() {
bool is_present;
WithOecReadLock("IsAntiRollbackHwPresent", [&] {
is_present = OEMCrypto_IsAntiRollbackHwPresent(requested_security_level_);
});
metrics_->oemcrypto_is_anti_rollback_hw_present_.Record(is_present);
return is_present;
}
CdmResponseType CryptoSession::GenerateNonce(uint32_t* nonce) {
RETURN_IF_NULL(nonce, PARAMETER_NULL);
OEMCryptoResult result;
WithOecWriteLock("GenerateNonce", [&] {
result = OEMCrypto_GenerateNonce(oec_session_id_, nonce);
});
metrics_->oemcrypto_generate_nonce_.Increment(result);
return MapOEMCryptoResult(result, NONCE_GENERATION_ERROR, "GenerateNonce");
}
bool CryptoSession::SetDestinationBufferType() {
if (Properties::oem_crypto_use_secure_buffers()) {
destination_buffer_type_ = OEMCrypto_BufferType_Secure;
} else if (Properties::oem_crypto_use_fifo()) {
destination_buffer_type_ = OEMCrypto_BufferType_Direct;
} else if (Properties::oem_crypto_use_userspace_buffers()) {
destination_buffer_type_ = OEMCrypto_BufferType_Clear;
} else {
return false;
}
is_destination_buffer_type_valid_ = true;
return true;
}
CdmResponseType CryptoSession::LoadProvisioning(
const std::string& signed_message, const std::string& core_message,
const std::string& signature, std::string* wrapped_private_key) {
LOGV("Loading provisioning certificate: id = %u", oec_session_id_);
if (wrapped_private_key == nullptr) {
LOGE("Missing wrapped |wrapped_private_key|");
return PARAMETER_NULL;
}
const std::string combined_message = core_message + signed_message;
// Round 1, get the size of the wrapped private key buffer.
size_t wrapped_private_key_length = 0;
OEMCryptoResult status;
WithOecSessionLock("LoadProvisioning Attempt 1", [&] {
M_TIME(status = OEMCrypto_LoadProvisioning(
oec_session_id_,
reinterpret_cast<const uint8_t*>(combined_message.data()),
combined_message.size(), core_message.size(),
reinterpret_cast<const uint8_t*>(signature.data()),
signature.size(), nullptr, &wrapped_private_key_length),
metrics_, oemcrypto_load_provisioning_, status);
});
if (status != OEMCrypto_ERROR_SHORT_BUFFER) {
return MapOEMCryptoResult(status, LOAD_PROVISIONING_ERROR,
"LoadProvisioning");
}
wrapped_private_key->resize(wrapped_private_key_length);
WithOecSessionLock("LoadProvisioning Attempt 2", [&] {
M_TIME(status = OEMCrypto_LoadProvisioning(
oec_session_id_,
reinterpret_cast<const uint8_t*>(combined_message.data()),
combined_message.size(), core_message.size(),
reinterpret_cast<const uint8_t*>(signature.data()),
signature.size(),
reinterpret_cast<uint8_t*>(&wrapped_private_key->front()),
&wrapped_private_key_length),
metrics_, oemcrypto_load_provisioning_, status);
});
if (status == OEMCrypto_SUCCESS) {
wrapped_private_key->resize(wrapped_private_key_length);
return NO_ERROR;
}
wrapped_private_key->clear();
return MapOEMCryptoResult(status, LOAD_PROVISIONING_ERROR,
"LoadProvisioning");
}
CdmResponseType CryptoSession::GetHdcpCapabilities(HdcpCapability* current,
HdcpCapability* max) {
LOGV("Getting HDCP capabilities: id = %u", oec_session_id_);
RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN);
return GetHdcpCapabilities(requested_security_level_, current, max);
}
CdmResponseType CryptoSession::GetHdcpCapabilities(
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);
OEMCryptoResult status;
WithOecReadLock("GetHdcpCapabilities", [&] {
status = OEMCrypto_GetHDCPCapability(security_level, current, max);
});
if (OEMCrypto_SUCCESS == status) {
metrics_->oemcrypto_current_hdcp_capability_.Record(*current);
metrics_->oemcrypto_max_hdcp_capability_.Record(*max);
} else {
metrics_->oemcrypto_current_hdcp_capability_.SetError(status);
metrics_->oemcrypto_max_hdcp_capability_.SetError(status);
}
return MapOEMCryptoResult(status, GET_HDCP_CAPABILITY_FAILED,
"GetHDCPCapability");
}
bool CryptoSession::GetSupportedCertificateTypes(
SupportedCertificateTypes* support) {
LOGV("Getting supported certificate types: id = %u", oec_session_id_);
RETURN_IF_UNINITIALIZED(false);
RETURN_IF_NULL(support, false);
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::GetRandom(size_t data_length,
uint8_t* random_data) {
RETURN_IF_NULL(random_data, PARAMETER_NULL);
OEMCryptoResult sts;
WithOecReadLock("GetRandom",
[&] { sts = OEMCrypto_GetRandom(random_data, data_length); });
metrics_->oemcrypto_get_random_.Increment(sts);
return MapOEMCryptoResult(sts, RANDOM_GENERATION_ERROR, "GetRandom");
}
CdmResponseType CryptoSession::GetNumberOfOpenSessions(
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 NO_ERROR;
case OEMCrypto_LOCAL_DISPLAY_ONLY:
LOGD("No SRM: Local display only");
return NO_SRM_VERSION;
case OEMCrypto_ERROR_NOT_IMPLEMENTED:
LOGD("No SRM: Not implemented");
return NO_SRM_VERSION;
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::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;
WithOecSessionLock("SetDecryptHash", [&] {
sts = OEMCrypto_SetDecryptHash(
oec_session_id_, frame_number,
reinterpret_cast<const uint8_t*>(hash.data()), hash.size());
metrics_->oemcrypto_set_decrypt_hash_.Increment(sts);
});
return MapOEMCryptoResult(sts, SET_DECRYPT_HASH_ERROR, "SetDecryptHash");
}
CdmResponseType CryptoSession::GetDecryptHashError(std::string* error_string) {
LOGV("Getting decrypt hash error");
RETURN_IF_NULL(error_string, PARAMETER_NULL);
error_string->clear();
uint32_t failed_frame_number = 0;
OEMCryptoResult sts;
WithOecSessionLock("GetDecryptHashError", [&] {
sts = OEMCrypto_GetHashErrorCode(oec_session_id_, &failed_frame_number);
});
error_string->assign(std::to_string(sts));
if (sts != OEMCrypto_SUCCESS) {
LOGE("OEMCrypto_GetHashErrorCode failed: status = %d",
static_cast<int>(sts));
}
switch (sts) {
case OEMCrypto_SUCCESS:
case OEMCrypto_ERROR_BAD_HASH:
error_string->assign(std::to_string(sts));
error_string->append(",");
error_string->append(std::to_string(failed_frame_number));
return NO_ERROR;
case OEMCrypto_ERROR_SESSION_LOST_STATE:
return SESSION_LOST_STATE_ERROR;
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
return SYSTEM_INVALIDATED_ERROR;
case OEMCrypto_ERROR_UNKNOWN_FAILURE:
case OEMCrypto_ERROR_NOT_IMPLEMENTED:
default:
return GET_DECRYPT_HASH_ERROR;
}
}
CdmResponseType CryptoSession::GenericEncrypt(const std::string& in_buffer,
const std::string& key_id,
const std::string& iv,
CdmEncryptionAlgorithm algorithm,
std::string* out_buffer) {
LOGV("Generic encrypt: id = %u", oec_session_id_);
RETURN_IF_NULL(out_buffer, PARAMETER_NULL);
OEMCrypto_Algorithm oec_algorithm = OEMCrypto_AES_CBC_128_NO_PADDING;
if (iv.size() != GenericEncryptionBlockSize(algorithm) ||
!GetGenericEncryptionAlgorithm(algorithm, &oec_algorithm)) {
return INVALID_PARAMETERS_ENG_13;
}
if (out_buffer->size() < in_buffer.size()) {
out_buffer->resize(in_buffer.size());
}
// TODO(jfore): We need to select a key with a cipher mode and algorithm
// doesn't seem to fit. Is it ok to just use a default value here?
// Or do we need to pass it in?
CdmResponseType result = SelectKey(key_id, kCipherModeCbc);
if (result != NO_ERROR) return result;
OEMCryptoResult sts;
WithOecSessionLock("GenericEncrypt", [&] {
M_TIME(
sts = OEMCrypto_Generic_Encrypt(
oec_session_id_, reinterpret_cast<const uint8_t*>(in_buffer.data()),
in_buffer.size(), reinterpret_cast<const uint8_t*>(iv.data()),
oec_algorithm,
reinterpret_cast<uint8_t*>(const_cast<char*>(out_buffer->data()))),
metrics_, oemcrypto_generic_encrypt_, sts,
metrics::Pow2Bucket(in_buffer.size()));
});
if (OEMCrypto_SUCCESS != sts) {
LOGE("OEMCrypto_Generic_Encrypt failed: status = %d",
static_cast<int>(sts));
}
switch (sts) {
case OEMCrypto_SUCCESS:
return NO_ERROR;
case OEMCrypto_ERROR_KEY_EXPIRED:
return NEED_KEY;
case OEMCrypto_ERROR_NO_CONTENT_KEY:
case OEMCrypto_KEY_NOT_LOADED: // obsolete in v15.
return KEY_NOT_FOUND_3;
case OEMCrypto_ERROR_OUTPUT_TOO_LARGE:
return OUTPUT_TOO_LARGE_ERROR;
case OEMCrypto_ERROR_SESSION_LOST_STATE:
return SESSION_LOST_STATE_ERROR;
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
return SYSTEM_INVALIDATED_ERROR;
default:
return UNKNOWN_ERROR;
}
}
CdmResponseType CryptoSession::GenericDecrypt(const std::string& in_buffer,
const std::string& key_id,
const std::string& iv,
CdmEncryptionAlgorithm algorithm,
std::string* out_buffer) {
LOGV("Generic decrypt: id = %u", oec_session_id_);
RETURN_IF_NULL(out_buffer, PARAMETER_NULL);
OEMCrypto_Algorithm oec_algorithm = OEMCrypto_AES_CBC_128_NO_PADDING;
if (iv.size() != GenericEncryptionBlockSize(algorithm) ||
!GetGenericEncryptionAlgorithm(algorithm, &oec_algorithm)) {
return INVALID_PARAMETERS_ENG_14;
}
if (out_buffer->size() < in_buffer.size()) {
out_buffer->resize(in_buffer.size());
}
// TODO(jfore): We need to select a key with a cipher mode and algorithm
// doesn't seem to fit. Is it ok to just use a default value here?
// Or do we need to pass it in?
CdmResponseType result = SelectKey(key_id, kCipherModeCbc);
if (result != NO_ERROR) return result;
OEMCryptoResult sts;
WithOecSessionLock("GenericDecrypt", [&] {
M_TIME(
sts = OEMCrypto_Generic_Decrypt(
oec_session_id_, reinterpret_cast<const uint8_t*>(in_buffer.data()),
in_buffer.size(), reinterpret_cast<const uint8_t*>(iv.data()),
oec_algorithm,
reinterpret_cast<uint8_t*>(const_cast<char*>(out_buffer->data()))),
metrics_, oemcrypto_generic_decrypt_, sts,
metrics::Pow2Bucket(in_buffer.size()));
});
if (OEMCrypto_SUCCESS != sts) {
LOGE("OEMCrypto_Generic_Decrypt failed: status = %d",
static_cast<int>(sts));
}
switch (sts) {
case OEMCrypto_SUCCESS:
return NO_ERROR;
case OEMCrypto_ERROR_KEY_EXPIRED:
return NEED_KEY;
case OEMCrypto_ERROR_NO_CONTENT_KEY:
case OEMCrypto_KEY_NOT_LOADED: // obsolete in v15.
return KEY_NOT_FOUND_4;
case OEMCrypto_ERROR_OUTPUT_TOO_LARGE:
return OUTPUT_TOO_LARGE_ERROR;
case OEMCrypto_ERROR_SESSION_LOST_STATE:
return SESSION_LOST_STATE_ERROR;
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
return SYSTEM_INVALIDATED_ERROR;
default:
return UNKNOWN_ERROR;
}
}
CdmResponseType CryptoSession::GenericSign(const std::string& message,
const std::string& key_id,
CdmSigningAlgorithm algorithm,
std::string* signature) {
LOGV("Generic sign: id = %u", oec_session_id_);
RETURN_IF_NULL(signature, PARAMETER_NULL);
OEMCrypto_Algorithm oec_algorithm = OEMCrypto_HMAC_SHA256;
if (!GetGenericSigningAlgorithm(algorithm, &oec_algorithm)) {
return INVALID_PARAMETERS_ENG_15;
}
OEMCryptoResult sts;
size_t length = signature->size();
// TODO(jfore): We need to select a key with a cipher mode and algorithm
// doesn't seem to fit. Is it ok to just use a default value here?
// Or do we need to pass it in?
CdmResponseType result = SelectKey(key_id, kCipherModeCbc);
if (result != NO_ERROR) return result;
// At most two attempts.
// The first attempt may fail due to buffer too short
for (int i = 0; i < 2; ++i) {
WithOecSessionLock("GenericSign", [&] {
M_TIME(
sts = OEMCrypto_Generic_Sign(
oec_session_id_, reinterpret_cast<const uint8_t*>(message.data()),
message.size(), oec_algorithm,
reinterpret_cast<uint8_t*>(const_cast<char*>(signature->data())),
&length),
metrics_, oemcrypto_generic_sign_, sts,
metrics::Pow2Bucket(message.size()));
});
if (OEMCrypto_SUCCESS == sts) {
// Trim signature buffer and done
signature->resize(length);
return NO_ERROR;
}
if (OEMCrypto_ERROR_SHORT_BUFFER != sts) {
break;
}
// Retry with proper-sized return buffer
signature->resize(length);
}
LOGE("OEMCrypto_Generic_Sign failed: status = %d", static_cast<int>(sts));
switch (sts) {
case OEMCrypto_ERROR_KEY_EXPIRED:
return NEED_KEY;
case OEMCrypto_ERROR_NO_CONTENT_KEY:
case OEMCrypto_KEY_NOT_LOADED: // obsolete in v15.
return KEY_NOT_FOUND_5;
case OEMCrypto_ERROR_OUTPUT_TOO_LARGE:
return OUTPUT_TOO_LARGE_ERROR;
case OEMCrypto_ERROR_SESSION_LOST_STATE:
return SESSION_LOST_STATE_ERROR;
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
return SYSTEM_INVALIDATED_ERROR;
default:
return UNKNOWN_ERROR;
}
}
CdmResponseType CryptoSession::GenericVerify(const std::string& message,
const std::string& key_id,
CdmSigningAlgorithm algorithm,
const std::string& signature) {
LOGV("Generic verify: id = %u", oec_session_id_);
OEMCrypto_Algorithm oec_algorithm = OEMCrypto_HMAC_SHA256;
if (!GetGenericSigningAlgorithm(algorithm, &oec_algorithm)) {
return INVALID_PARAMETERS_ENG_16;
}
// TODO(jfore): We need to select a key with a cipher mode and algorithm
// doesn't seem to fit. Is it ok to just use a default value here?
// Or do we need to pass it in?
CdmResponseType result = SelectKey(key_id, kCipherModeCbc);
if (result != NO_ERROR) return result;
OEMCryptoResult sts;
WithOecSessionLock("GenericVerify", [&] {
M_TIME(
sts = OEMCrypto_Generic_Verify(
oec_session_id_, reinterpret_cast<const uint8_t*>(message.data()),
message.size(), oec_algorithm,
reinterpret_cast<const uint8_t*>(signature.data()),
signature.size()),
metrics_, oemcrypto_generic_verify_, sts,
metrics::Pow2Bucket(signature.size()));
});
if (OEMCrypto_SUCCESS != sts) {
LOGE("OEMCrypto_Generic_Verify failed: status = %d", static_cast<int>(sts));
}
switch (sts) {
case OEMCrypto_SUCCESS:
return NO_ERROR;
case OEMCrypto_ERROR_KEY_EXPIRED:
return NEED_KEY;
case OEMCrypto_ERROR_NO_CONTENT_KEY:
case OEMCrypto_KEY_NOT_LOADED: // obsolete in v15.
return KEY_NOT_FOUND_6;
case OEMCrypto_ERROR_OUTPUT_TOO_LARGE:
return OUTPUT_TOO_LARGE_ERROR;
case OEMCrypto_ERROR_SESSION_LOST_STATE:
return SESSION_LOST_STATE_ERROR;
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
return SYSTEM_INVALIDATED_ERROR;
default:
return UNKNOWN_ERROR;
}
}
CdmResponseType CryptoSession::CreateUsageTableHeader(
RequestedSecurityLevel requested_security_level,
CdmUsageTableHeader* 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 NO_ERROR;
default:
return MapOEMCryptoResult(result, CREATE_USAGE_TABLE_ERROR,
"CreateUsageTableHeader");
}
}
CdmResponseType CryptoSession::LoadUsageTableHeader(
RequestedSecurityLevel requested_security_level,
const CdmUsageTableHeader& 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 NO_ERROR;
case OEMCrypto_ERROR_GENERATION_SKEW:
return LOAD_USAGE_HEADER_GENERATION_SKEW;
case OEMCrypto_ERROR_SIGNATURE_FAILURE:
return LOAD_USAGE_HEADER_SIGNATURE_FAILURE;
case OEMCrypto_ERROR_BAD_MAGIC:
return LOAD_USAGE_HEADER_BAD_MAGIC;
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
return SYSTEM_INVALIDATED_ERROR;
case OEMCrypto_ERROR_UNKNOWN_FAILURE:
default:
return LOAD_USAGE_HEADER_UNKNOWN_ERROR;
}
}
CdmResponseType CryptoSession::ShrinkUsageTableHeader(
RequestedSecurityLevel requested_security_level, uint32_t new_entry_count,
CdmUsageTableHeader* 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 NO_ERROR;
case OEMCrypto_ERROR_ENTRY_IN_USE:
return SHRINK_USAGE_TABLE_HEADER_ENTRY_IN_USE;
default:
return MapOEMCryptoResult(result, SHRINK_USAGE_TABLE_HEADER_UNKNOWN_ERROR,
"ShrinkUsageTableHeader");
}
}
CdmResponseType CryptoSession::CreateUsageEntry(uint32_t* entry_number) {
LOGV("Creating usage entry: id = %u", oec_session_id_);
RETURN_IF_NULL(entry_number, PARAMETER_NULL);
OEMCryptoResult result;
WithOecWriteLock("CreateUsageEntry", [&] {
result = OEMCrypto_CreateNewUsageEntry(oec_session_id_, entry_number);
metrics_->oemcrypto_create_new_usage_entry_.Increment(result);
});
if (result != OEMCrypto_SUCCESS) {
LOGE("OEMCrypto_CreateNewUsageEntry failed: status = %d",
static_cast<int>(result));
}
switch (result) {
case OEMCrypto_SUCCESS:
return NO_ERROR;
case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES:
return INSUFFICIENT_CRYPTO_RESOURCES;
case OEMCrypto_ERROR_SESSION_LOST_STATE:
return SESSION_LOST_STATE_ERROR;
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
return SYSTEM_INVALIDATED_ERROR;
default:
return CREATE_USAGE_ENTRY_UNKNOWN_ERROR;
}
}
CdmResponseType CryptoSession::LoadUsageEntry(
uint32_t entry_number, const CdmUsageEntry& usage_entry) {
LOGV("Loading usage entry: id = %u", oec_session_id_);
OEMCryptoResult result;
WithOecWriteLock("LoadUsageEntry", [&] {
result = OEMCrypto_LoadUsageEntry(
oec_session_id_, entry_number,
reinterpret_cast<const uint8_t*>(usage_entry.data()),
usage_entry.size());
metrics_->oemcrypto_load_usage_entry_.Increment(result);
});
if (result != OEMCrypto_SUCCESS) {
if (result == OEMCrypto_WARNING_GENERATION_SKEW) {
LOGW("OEMCrypto_LoadUsageEntry warning: generation skew");
} else {
LOGE("OEMCrypto_LoadUsageEntry failed: status = %d",
static_cast<int>(result));
}
}
switch (result) {
case OEMCrypto_SUCCESS:
case OEMCrypto_WARNING_GENERATION_SKEW:
return NO_ERROR;
case OEMCrypto_ERROR_INVALID_SESSION:
// This case is special, as it could imply that the provided
// session ID is invalid (CDM internal bug), or that the entry
// being loaded is already in use in a different session.
// It is up to the caller to handle this.
return LOAD_USAGE_ENTRY_INVALID_SESSION;
case OEMCrypto_ERROR_GENERATION_SKEW:
return LOAD_USAGE_ENTRY_GENERATION_SKEW;
case OEMCrypto_ERROR_SIGNATURE_FAILURE:
return LOAD_USAGE_ENTRY_SIGNATURE_FAILURE;
default:
return MapOEMCryptoResult(result, LOAD_USAGE_ENTRY_UNKNOWN_ERROR,
"LoadUsageEntry");
}
}
CdmResponseType CryptoSession::UpdateUsageEntry(
CdmUsageTableHeader* usage_table_header, CdmUsageEntry* usage_entry) {
LOGV("Updating usage entry: id = %u", oec_session_id_);
RETURN_IF_NULL(usage_table_header, PARAMETER_NULL);
RETURN_IF_NULL(usage_entry, PARAMETER_NULL);
size_t usage_table_header_len = 0;
size_t usage_entry_len = 0;
OEMCryptoResult result;
WithOecWriteLock("UpdateUsageEntry Attempt 1", [&] {
result = OEMCrypto_UpdateUsageEntry(oec_session_id_, nullptr,
&usage_table_header_len, nullptr,
&usage_entry_len);
});
metrics_->oemcrypto_update_usage_entry_.Increment(result);
if (result == OEMCrypto_ERROR_SHORT_BUFFER) {
usage_table_header->resize(usage_table_header_len);
usage_entry->resize(usage_entry_len);
WithOecWriteLock("UpdateUsageEntry Attempt 2", [&] {
result = OEMCrypto_UpdateUsageEntry(
oec_session_id_,
reinterpret_cast<uint8_t*>(
const_cast<char*>(usage_table_header->data())),
&usage_table_header_len,
reinterpret_cast<uint8_t*>(const_cast<char*>(usage_entry->data())),
&usage_entry_len);
});
metrics_->oemcrypto_update_usage_entry_.Increment(result);
}
if (result == OEMCrypto_SUCCESS) {
usage_table_header->resize(usage_table_header_len);
usage_entry->resize(usage_entry_len);
}
return MapOEMCryptoResult(result, UPDATE_USAGE_ENTRY_UNKNOWN_ERROR,
"UpdateUsageEntry");
}
CdmResponseType CryptoSession::MoveUsageEntry(uint32_t new_entry_number) {
LOGV("Moving usage entry: id = %u", oec_session_id_);
OEMCryptoResult result;
WithOecWriteLock("MoveUsageEntry", [&] {
result = OEMCrypto_MoveEntry(oec_session_id_, new_entry_number);
metrics_->oemcrypto_move_entry_.Increment(result);
});
switch (result) {
case OEMCrypto_ERROR_ENTRY_IN_USE:
LOGW("OEMCrypto_MoveEntry failed: Destination index in use: index = %u",
new_entry_number);
return MOVE_USAGE_ENTRY_DESTINATION_IN_USE;
default:
return MapOEMCryptoResult(result, MOVE_USAGE_ENTRY_UNKNOWN_ERROR,
"MoveUsageEntry");
}
}
bool CryptoSession::GetAnalogOutputCapabilities(bool* can_support_output,
bool* can_disable_output,
bool* can_support_cgms_a) {
LOGV("Getting analog output capabilities: id = %u", oec_session_id_);
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 OEMCrypto_DecryptCENC(
// OEMCrypto_SESSION session,
// const OEMCrypto_SampleDescription* samples, // an array of samples.
// size_t samples_length, // the number of samples.
// const OEMCrypto_CENCEncryptPatternDesc* pattern);
OEMCryptoResult CryptoSession::DecryptMultipleSamples(
const std::vector<OEMCrypto_SampleDescription>& samples,
CdmCipherMode cipher_mode,
const OEMCrypto_CENCEncryptPatternDesc& pattern) {
OEMCryptoResult sts = OEMCrypto_ERROR_BUFFER_TOO_LARGE;
// If there's only one sample, automatically fall through to avoid a redundant
// roundtrip through OEMCrypto_DecryptCENC()
if (samples.size() > 1) {
WithOecSessionLock("DecryptMultipleSamples", [&] {
sts = key_session_->Decrypt(samples.data(), samples.size(), pattern);
});
}
if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE) {
// Fall back to sending each sample individually
for (const OEMCrypto_SampleDescription& sample : samples) {
sts = DecryptSample(sample, cipher_mode, pattern);
if (sts != OEMCrypto_SUCCESS) break;
}
}
return sts;
}
OEMCryptoResult CryptoSession::DecryptSample(
const OEMCrypto_SampleDescription& sample, CdmCipherMode cipher_mode,
const OEMCrypto_CENCEncryptPatternDesc& pattern) {
OEMCryptoResult sts = OEMCrypto_ERROR_BUFFER_TOO_LARGE;
// If there's only one subsample and it contains only one type of region,
// automatically fall through to avoid a redundant roundtrip through
// OEMCrypto_DecryptCENC()
if (sample.subsamples_length > 1 ||
(sample.subsamples[0].num_bytes_clear > 0 &&
sample.subsamples[0].num_bytes_encrypted > 0)) {
WithOecSessionLock("DecryptSample", [&] {
sts = key_session_->Decrypt(&sample, 1, pattern);
});
}
if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE) {
// Fall back to sending each subsample region individually
sts = OEMCrypto_SUCCESS;
OEMCrypto_SampleDescription fake_sample = sample;
for (size_t i = 0; i < sample.subsamples_length; ++i) {
const OEMCrypto_SubSampleDescription& original_subsample =
sample.subsamples[i];
if (original_subsample.num_bytes_clear > 0) {
const size_t length = original_subsample.num_bytes_clear;
OEMCrypto_SubSampleDescription clear_subsample{
length,
0, // num_bytes_encrypted
0, // subsample_flags
0 // block_offset, not relevant for clear data
};
if (original_subsample.subsample_flags & OEMCrypto_FirstSubsample) {
clear_subsample.subsample_flags |= OEMCrypto_FirstSubsample;
}
if ((original_subsample.subsample_flags & OEMCrypto_LastSubsample) &&
original_subsample.num_bytes_encrypted == 0) {
clear_subsample.subsample_flags |= OEMCrypto_LastSubsample;
}
fake_sample.buffers.input_data_length = length;
fake_sample.subsamples = &clear_subsample;
fake_sample.subsamples_length = 1;
sts = LegacyDecrypt(fake_sample, cipher_mode, pattern);
if (sts != OEMCrypto_SUCCESS) break;
fake_sample.buffers.input_data += length;
AdvanceDestBuffer(&fake_sample.buffers.output_descriptor, length);
}
if (original_subsample.num_bytes_encrypted > 0) {
const size_t length = original_subsample.num_bytes_encrypted;
OEMCrypto_SubSampleDescription encrypted_subsample{
0, // num_bytes_clear
length,
0, // subsample_flags
original_subsample.block_offset};
if ((original_subsample.subsample_flags & OEMCrypto_FirstSubsample) &&
original_subsample.num_bytes_clear == 0) {
encrypted_subsample.subsample_flags |= OEMCrypto_FirstSubsample;
}
if (original_subsample.subsample_flags & OEMCrypto_LastSubsample) {
encrypted_subsample.subsample_flags |= OEMCrypto_LastSubsample;
}
fake_sample.buffers.input_data_length = length;
fake_sample.subsamples = &encrypted_subsample;
fake_sample.subsamples_length = 1;
sts = LegacyDecrypt(fake_sample, cipher_mode, pattern);
if (sts != OEMCrypto_SUCCESS) break;
fake_sample.buffers.input_data += length;
AdvanceDestBuffer(&fake_sample.buffers.output_descriptor, length);
if (cipher_mode == kCipherModeCtr) {
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) {
const size_t max_chunk_size = GetMaxSubsampleRegionSize();
OEMCryptoResult sts = OEMCrypto_ERROR_NOT_IMPLEMENTED;
// We can be sure this is only called with one subsample containing one
// region of data.
const OEMCrypto_SubSampleDescription& subsample = sample.subsamples[0];
const bool is_encrypted = (subsample.num_bytes_encrypted > 0);
const bool is_only_subsample =
subsample.subsample_flags ==
(OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample);
if (!is_encrypted && is_only_subsample) {
WithOecSessionLock("LegacyDecrypt() calling CopyBuffer", [&] {
M_TIME(sts = OEMCrypto_CopyBuffer(
oec_session_id_, sample.buffers.input_data,
sample.buffers.input_data_length,
&sample.buffers.output_descriptor, subsample.subsample_flags),
metrics_, oemcrypto_copy_buffer_, sts,
metrics::Pow2Bucket(sample.buffers.input_data_length));
});
if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE &&
sample.buffers.input_data_length > max_chunk_size) {
// OEMCrypto_CopyBuffer rejected the buffer as too large, so chunk it up
// into 100 KiB sections.
sts = LegacyCopyBufferInChunks(sample, max_chunk_size);
}
}
if (is_encrypted || sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) {
WithOecSessionLock("LegacyDecrypt() calling key_session_->Decrypt()", [&] {
sts = key_session_->Decrypt(&sample, 1, pattern);
});
if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE) {
// OEMCrypto_DecryptCENC rejected the buffer as too large, so chunk it
// up into sections no more than 100 KiB. The exact chunk size needs to
// be an even number of pattern repetitions long or else the pattern
// will get out of sync.
const size_t pattern_length =
(pattern.encrypt + pattern.skip) * kAes128BlockSize;
const size_t chunk_size =
pattern_length > 0
? max_chunk_size - (max_chunk_size % pattern_length)
: max_chunk_size;
if (sample.buffers.input_data_length > chunk_size) {
sts = LegacyDecryptInChunks(sample, cipher_mode, pattern, chunk_size);
}
}
}
return sts;
}
OEMCryptoResult CryptoSession::LegacyCopyBufferInChunks(
const OEMCrypto_SampleDescription& sample, size_t max_chunk_size) {
const uint8_t* input_data = sample.buffers.input_data;
size_t remaining_input_data = sample.buffers.input_data_length;
OEMCrypto_DestBufferDesc output_descriptor = sample.buffers.output_descriptor;
uint8_t subsample_flags = OEMCrypto_FirstSubsample;
OEMCryptoResult sts = OEMCrypto_SUCCESS;
while (remaining_input_data > 0) {
// Calculate the size of the next chunk.
const size_t chunk_size = std::min(remaining_input_data, max_chunk_size);
// Re-add "last subsample" flag if this is the last subsample.
if (chunk_size == remaining_input_data) {
subsample_flags |= OEMCrypto_LastSubsample;
}
WithOecSessionLock("LegacyCopyBufferInChunks", [&] {
M_TIME(sts = OEMCrypto_CopyBuffer(oec_session_id_, input_data, chunk_size,
&output_descriptor, subsample_flags),
metrics_, oemcrypto_copy_buffer_, sts,
metrics::Pow2Bucket(chunk_size));
});
if (sts != OEMCrypto_SUCCESS) break;
// Clear any subsample flags before the next loop iteration.
subsample_flags = 0;
// Update the source and destination buffers based on the amount of data
// copied.
input_data += chunk_size;
remaining_input_data -= chunk_size;
AdvanceDestBuffer(&output_descriptor, chunk_size);
}
return sts;
}
OEMCryptoResult CryptoSession::LegacyDecryptInChunks(
const OEMCrypto_SampleDescription& sample, CdmCipherMode cipher_mode,
const OEMCrypto_CENCEncryptPatternDesc& pattern, size_t max_chunk_size) {
const OEMCrypto_SubSampleDescription& subsample = sample.subsamples[0];
const bool is_protected = (subsample.num_bytes_encrypted > 0);
OEMCrypto_SampleDescription fake_sample = sample;
OEMCrypto_SubSampleDescription fake_subsample = subsample;
fake_sample.subsamples = &fake_subsample;
fake_subsample.subsample_flags =
subsample.subsample_flags & OEMCrypto_FirstSubsample;
size_t remaining_input_data = sample.buffers.input_data_length;
OEMCryptoResult sts = OEMCrypto_SUCCESS;
while (remaining_input_data > 0) {
// Calculate the size of the next chunk.
const size_t chunk_size = std::min(remaining_input_data, max_chunk_size);
fake_sample.buffers.input_data_length = chunk_size;
if (is_protected) {
fake_subsample.num_bytes_encrypted = chunk_size;
} else {
fake_subsample.num_bytes_clear = chunk_size;
}
// Re-add "last subsample" flag if this is the last subsample.
if (chunk_size == remaining_input_data) {
fake_subsample.subsample_flags |=
subsample.subsample_flags & OEMCrypto_LastSubsample;
}
// |pattern| and |fake_subsample.block_offset| do not need to change because
// |max_chunk_size| is guaranteed to be an even multiple of the
// pattern length long, which is also guaranteed to be an exact number
// of AES blocks long.
WithOecSessionLock("LegacyDecryptInChunks", [&] {
sts = key_session_->Decrypt(&fake_sample, 1, pattern);
});
if (sts != OEMCrypto_SUCCESS) break;
// Clear any subsample flags before the next loop iteration.
fake_subsample.subsample_flags = 0;
// Update the IV so that it is valid for the next iteration. This should not
// be done on the last iteration both to save time and because the 'cbcs'
// calculation can underflow if the chunk is less than the max chunk size.
if (remaining_input_data > chunk_size) {
if (cipher_mode == kCipherModeCtr) {
// For 'cenc', update the IV depending on how many encrypted blocks
// we passed.
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");
}
CdmResponseType CryptoSession::SetAllowTestKeybox(bool allow) {
OEMCryptoResult status = OEMCrypto_SetAllowTestKeybox(allow);
return MapOEMCryptoResult(status, UNKNOWN_ERROR, "SetAllowTestKeybox");
}
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 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; });
}
CdmResponseType result = GetSystemIdInternal(&system_id_);
if (result == NO_ERROR) {
LOGD("New system id is %d", system_id_);
} else {
LOGE("Failed to fetch system ID");
}
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