Revert submission 28914157 Reason for revert: b/372348308 Reverted changes: /q/submissionid:28914157 Change-Id: Ib77156ffe6abed0f8feee5d9f60f24a90e749ff8
3613 lines
137 KiB
C++
3613 lines
137 KiB
C++
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
|
// source code may only be used and distributed under the Widevine License
|
|
// Agreement.
|
|
//
|
|
// Crypto - wrapper classes for OEMCrypto interface
|
|
//
|
|
|
|
#include "crypto_session.h"
|
|
|
|
#include <inttypes.h>
|
|
#include <string.h>
|
|
|
|
#include <algorithm>
|
|
#include <iostream>
|
|
#include <memory>
|
|
|
|
#include "advance_iv_ctr.h"
|
|
#include "arraysize.h"
|
|
#include "cdm_random.h"
|
|
#include "cdm_usage_table.h"
|
|
#include "content_key_session.h"
|
|
#include "crypto_key.h"
|
|
#include "entitlement_key_session.h"
|
|
#include "log.h"
|
|
#include "odk_structs.h"
|
|
#include "okp_fallback_policy.h"
|
|
#include "platform.h"
|
|
#include "privacy_crypto.h"
|
|
#include "properties.h"
|
|
#include "pst_report.h"
|
|
#include "string_conversions.h"
|
|
#include "wv_cdm_constants.h"
|
|
|
|
// Stringify turns macro arguments into static C strings.
|
|
// Example: STRINGIFY(this_argument) -> "this_argument"
|
|
#define STRINGIFY(PARAM) #PARAM
|
|
|
|
namespace {
|
|
bool WrapIfNecessary(bool ret_value) { return ret_value; }
|
|
|
|
wvcdm::CdmResponseType WrapIfNecessary(wvcdm::CdmResponseEnum ret_value) {
|
|
return wvcdm::CdmResponseType(ret_value);
|
|
}
|
|
|
|
wvcdm::CdmSecurityLevel WrapIfNecessary(wvcdm::CdmSecurityLevel ret_value) {
|
|
return ret_value;
|
|
}
|
|
|
|
OEMCryptoResult WrapIfNecessary(OEMCryptoResult ret_value) { return ret_value; }
|
|
} // namespace
|
|
|
|
#define RETURN_IF_NULL(PARAM, ret_value) \
|
|
if ((PARAM) == nullptr) { \
|
|
LOGE("Output parameter |" STRINGIFY(PARAM) "| not provided"); \
|
|
return WrapIfNecessary(ret_value); \
|
|
}
|
|
|
|
#define RETURN_IF_UNINITIALIZED(ret_value) \
|
|
if (!IsInitialized()) { \
|
|
LOGE("Crypto session is not initialized"); \
|
|
return WrapIfNecessary(ret_value); \
|
|
}
|
|
|
|
#define RETURN_IF_NOT_OPEN(ret_value) \
|
|
if (!IsOpen()) { \
|
|
LOGE("Crypto session is not open"); \
|
|
return WrapIfNecessary(ret_value); \
|
|
}
|
|
|
|
#define CRYPTO_ERROR(cdm_err, oem_err) \
|
|
CdmResponseType(cdm_err, oem_err, __func__)
|
|
|
|
#ifdef HAS_DUAL_KEY
|
|
/**
|
|
* Internal only OEMCrypto method. This is called before parsing the license
|
|
* response to indicate the response uses dual keys. If this isn't called,
|
|
* it is using single keys.
|
|
*/
|
|
extern "C" OEMCryptoResult OEMCrypto_UseSecondaryKey(OEMCrypto_SESSION session,
|
|
bool dual_key);
|
|
#endif
|
|
|
|
namespace wvcdm {
|
|
|
|
namespace {
|
|
using UsageTableLock = std::unique_lock<std::recursive_mutex>;
|
|
|
|
constexpr size_t KiB = 1024;
|
|
constexpr size_t MiB = 1024 * 1024;
|
|
|
|
constexpr uint32_t kRsaSignatureLength = 256;
|
|
constexpr size_t kEstimatedInitialUsageTableHeader = 40;
|
|
constexpr size_t kAes128BlockSize = 16;
|
|
|
|
constexpr int kMaxTerminateCountDown = 5;
|
|
|
|
constexpr char kUnavailableHdcpLevel[] = "<unavailable>";
|
|
|
|
const std::string kStringNotAvailable = "NA";
|
|
|
|
// TODO(b/174412779): Remove when b/170704368 is fixed.
|
|
// This is a Qualcomm specific error code
|
|
const int kRsaSsaPssSignatureLengthError = 10085;
|
|
|
|
// Constants relating to OEMCrypto resource rating tiers. These tables are
|
|
// ordered by resource rating tier from lowest to highest. These should be
|
|
// updated whenever the supported range of resource rating tiers changes.
|
|
constexpr size_t kMaxSubsampleRegionSizes[] = {
|
|
100 * KiB, // Tier 1 - Low
|
|
500 * KiB, // Tier 2 - Medium
|
|
1 * MiB, // Tier 3 - High
|
|
4 * MiB, // Tier 4 - Very High
|
|
};
|
|
// The +1 in this calculation is because the bounds RESOURCE_RATING_TIER_MAX and
|
|
// RESOURCE_RATING_TIER_MIN are inclusive.
|
|
static_assert(wvutil::ArraySize(kMaxSubsampleRegionSizes) ==
|
|
RESOURCE_RATING_TIER_MAX - RESOURCE_RATING_TIER_MIN + 1,
|
|
"The kMaxSubsampleRegionSizes table needs to be updated to "
|
|
"reflect the supported range of resource rating tiers");
|
|
|
|
constexpr size_t kDefaultMaxSubsampleRegionSize = kMaxSubsampleRegionSizes[0];
|
|
|
|
constexpr size_t kMaxExternalDeviceIdLength = 64;
|
|
|
|
// This maps a few common OEMCryptoResult to CdmResponseType. Many mappings
|
|
// are not universal but are OEMCrypto method specific. Those will be
|
|
// specified in the CryptoSession method rather than here.
|
|
CdmResponseType MapOEMCryptoResult(OEMCryptoResult result,
|
|
CdmResponseEnum default_status,
|
|
const char* crypto_session_method) {
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Mapping OEMCrypto result: crypto_session_method = %s, result = %d",
|
|
crypto_session_method ? crypto_session_method : "N/A",
|
|
static_cast<int>(result));
|
|
}
|
|
|
|
CdmResponseEnum status{};
|
|
switch (result) {
|
|
case OEMCrypto_SUCCESS:
|
|
status = NO_ERROR;
|
|
break;
|
|
case OEMCrypto_ERROR_NOT_IMPLEMENTED:
|
|
status = NOT_IMPLEMENTED_ERROR;
|
|
break;
|
|
case OEMCrypto_ERROR_TOO_MANY_SESSIONS:
|
|
status = INSUFFICIENT_CRYPTO_RESOURCES;
|
|
break;
|
|
case OEMCrypto_ERROR_SESSION_LOST_STATE:
|
|
status = SESSION_LOST_STATE_ERROR;
|
|
break;
|
|
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
|
|
status = SYSTEM_INVALIDATED_ERROR;
|
|
break;
|
|
default:
|
|
status = default_status;
|
|
}
|
|
return CdmResponseType(status, result, crypto_session_method);
|
|
}
|
|
|
|
void AdvanceDestBuffer(OEMCrypto_DestBufferDesc* dest_buffer, size_t bytes) {
|
|
switch (dest_buffer->type) {
|
|
case OEMCrypto_BufferType_Clear:
|
|
dest_buffer->buffer.clear.clear_buffer += bytes;
|
|
dest_buffer->buffer.clear.clear_buffer_length -= bytes;
|
|
return;
|
|
|
|
case OEMCrypto_BufferType_Secure:
|
|
dest_buffer->buffer.secure.offset += bytes;
|
|
return;
|
|
|
|
case OEMCrypto_BufferType_Direct:
|
|
// Nothing to do for this buffer type.
|
|
return;
|
|
}
|
|
LOGE("Unrecognized OEMCryptoBufferType %u - doing nothing",
|
|
static_cast<unsigned int>(dest_buffer->type));
|
|
}
|
|
|
|
bool GetGenericSigningAlgorithm(CdmSigningAlgorithm algorithm,
|
|
OEMCrypto_Algorithm* oec_algorithm) {
|
|
RETURN_IF_NULL(oec_algorithm, false);
|
|
if (kSigningAlgorithmHmacSha256 != algorithm) {
|
|
LOGW("Unrecognized signing algorithm: %d", algorithm);
|
|
return false;
|
|
}
|
|
*oec_algorithm = OEMCrypto_HMAC_SHA256;
|
|
return true;
|
|
}
|
|
|
|
bool GetGenericEncryptionAlgorithm(CdmEncryptionAlgorithm algorithm,
|
|
OEMCrypto_Algorithm* oec_algorithm) {
|
|
RETURN_IF_NULL(oec_algorithm, false);
|
|
if (kEncryptionAlgorithmAesCbc128 != algorithm) {
|
|
LOGW("Unrecognized encryption algorithm: %d", algorithm);
|
|
return false;
|
|
}
|
|
*oec_algorithm = OEMCrypto_AES_CBC_128_NO_PADDING;
|
|
return true;
|
|
}
|
|
|
|
size_t GenericEncryptionBlockSize(CdmEncryptionAlgorithm algorithm) {
|
|
if (kEncryptionAlgorithmAesCbc128 != algorithm) {
|
|
LOGW("Unrecognized encryption algorithm: %d", algorithm);
|
|
return 0;
|
|
}
|
|
return kAes128BlockSize;
|
|
}
|
|
|
|
uint8_t* MutableStringDataPointer(std::string* s) {
|
|
if (s == nullptr) return nullptr;
|
|
if (s->empty()) return nullptr;
|
|
return reinterpret_cast<uint8_t*>(&s->front());
|
|
}
|
|
} // namespace
|
|
|
|
// CryptoSession variables allocation.
|
|
wvutil::shared_mutex CryptoSession::static_field_mutex_;
|
|
wvutil::shared_mutex CryptoSession::oem_crypto_mutex_;
|
|
bool CryptoSession::initialized_ = false;
|
|
bool CryptoSession::needs_keybox_provisioning_ = false;
|
|
int CryptoSession::session_count_ = 0;
|
|
int CryptoSession::termination_counter_ = 0;
|
|
std::unique_ptr<CdmUsageTable> CryptoSession::usage_table_l1_;
|
|
std::unique_ptr<CdmUsageTable> CryptoSession::usage_table_l3_;
|
|
std::recursive_mutex CryptoSession::usage_table_mutex_;
|
|
std::atomic<uint64_t> CryptoSession::request_id_index_source_(0);
|
|
std::unique_ptr<okp::SystemFallbackPolicy>
|
|
CryptoSession::okp_fallback_policy_l1_;
|
|
|
|
size_t GetOffset(std::string message, std::string field) {
|
|
size_t pos = message.find(field);
|
|
if (pos == std::string::npos) {
|
|
LOGE("Cannot find the |field| offset in message: field = %s",
|
|
IdToString(field));
|
|
pos = 0;
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
OEMCrypto_Substring GetSubstring(const std::string& message,
|
|
const std::string& field, bool set_zero) {
|
|
OEMCrypto_Substring substring;
|
|
if (set_zero || field.empty() || message.empty()) {
|
|
substring.offset = 0;
|
|
substring.length = 0;
|
|
} else {
|
|
size_t pos = message.find(field);
|
|
if (pos == std::string::npos) {
|
|
LOGW("Cannot find the |field| substring in message: field = %s",
|
|
IdToString(field));
|
|
substring.offset = 0;
|
|
substring.length = 0;
|
|
} else {
|
|
substring.offset = pos;
|
|
substring.length = field.length();
|
|
}
|
|
}
|
|
return substring;
|
|
}
|
|
|
|
OEMCryptoCipherMode ToOEMCryptoCipherMode(CdmCipherMode cipher_mode) {
|
|
return cipher_mode == kCipherModeCtr ? OEMCrypto_CipherMode_CENC
|
|
: OEMCrypto_CipherMode_CBCS;
|
|
}
|
|
|
|
// static
|
|
const char* CryptoSession::HdcpCapabilityToString(HdcpCapability hdcp_level) {
|
|
switch (hdcp_level) {
|
|
case HDCP_NONE:
|
|
return "None";
|
|
case HDCP_V1: // Means v1.x
|
|
return "v1.x";
|
|
case HDCP_V2: // Means v2.0
|
|
return "v2.0";
|
|
case HDCP_V2_1:
|
|
return "v2.1";
|
|
case HDCP_V2_2:
|
|
return "v2.2";
|
|
case HDCP_V2_3:
|
|
return "v2.3";
|
|
case HDCP_V1_0:
|
|
return "v1.0";
|
|
case HDCP_V1_1:
|
|
return "v1.1";
|
|
case HDCP_V1_2:
|
|
return "v1.2";
|
|
case HDCP_V1_3:
|
|
return "v1.3";
|
|
case HDCP_V1_4:
|
|
return "v1.4";
|
|
case HDCP_NO_DIGITAL_OUTPUT:
|
|
return "No-Digital-Output";
|
|
}
|
|
return UnknownEnumValueToString(static_cast<int>(hdcp_level));
|
|
}
|
|
|
|
CryptoSession::CryptoSession(metrics::CryptoMetrics* metrics)
|
|
: metrics_(metrics),
|
|
system_id_(NULL_SYSTEM_ID),
|
|
open_(false),
|
|
pre_provision_token_type_(kClientTokenUninitialized),
|
|
update_usage_table_after_close_session_(false),
|
|
is_destination_buffer_type_valid_(false),
|
|
requested_security_level_(kLevelDefault),
|
|
api_version_(0),
|
|
max_subsample_region_size_(0) {
|
|
assert(metrics);
|
|
Init();
|
|
life_span_.Start();
|
|
}
|
|
|
|
CryptoSession::~CryptoSession() {
|
|
if (open_) {
|
|
Close();
|
|
}
|
|
WithStaticFieldWriteLock("~CryptoSession", [&] {
|
|
if (session_count_ > 0) {
|
|
--session_count_;
|
|
} else {
|
|
LOGE("Invalid crypto session count: session_count_ = %d", session_count_);
|
|
}
|
|
});
|
|
|
|
TryTerminate();
|
|
M_RECORD(metrics_, crypto_session_life_span_, life_span_.AsMs());
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetProvisioningMethod(
|
|
RequestedSecurityLevel requested_security_level,
|
|
CdmClientTokenType* token_type) {
|
|
OEMCrypto_ProvisioningMethod method;
|
|
WithOecReadLock("GetProvisioningMethod", [&] {
|
|
method = OEMCrypto_GetProvisioningMethod(requested_security_level);
|
|
});
|
|
metrics_->oemcrypto_provisioning_method_.Record(method);
|
|
CdmClientTokenType type;
|
|
switch (method) {
|
|
case OEMCrypto_OEMCertificate:
|
|
type = kClientTokenOemCert;
|
|
break;
|
|
case OEMCrypto_Keybox:
|
|
type = kClientTokenKeybox;
|
|
break;
|
|
case OEMCrypto_DrmCertificate:
|
|
type = kClientTokenDrmCert;
|
|
break;
|
|
case OEMCrypto_BootCertificateChain:
|
|
type = kClientTokenBootCertChain;
|
|
break;
|
|
case OEMCrypto_DrmReprovisioning:
|
|
type = kClientTokenDrmCertificateReprovisioning;
|
|
break;
|
|
case OEMCrypto_ProvisioningError:
|
|
default:
|
|
if (static_cast<int>(method) == 0 && needs_keybox_provisioning_) {
|
|
LOGW("Overriding provisioning method, assuming keybox");
|
|
type = kClientTokenKeybox;
|
|
break;
|
|
}
|
|
LOGE("OEMCrypto_GetProvisioningMethod failed: method = %d",
|
|
static_cast<int>(method));
|
|
metrics_->oemcrypto_provisioning_method_.SetError(method);
|
|
return CdmResponseType(GET_PROVISIONING_METHOD_ERROR);
|
|
}
|
|
*token_type = type;
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
|
|
void CryptoSession::Init() {
|
|
LOGV("Initializing crypto session");
|
|
bool initialized = false;
|
|
WithStaticFieldWriteLock("Init", [&] {
|
|
session_count_ += 1;
|
|
if (!initialized_) {
|
|
std::string sandbox_id;
|
|
OEMCryptoResult sts;
|
|
WithOecWriteLock("Init", [&] {
|
|
if (Properties::GetSandboxId(&sandbox_id) && !sandbox_id.empty()) {
|
|
sts = OEMCrypto_SetSandbox(
|
|
reinterpret_cast<const uint8_t*>(sandbox_id.c_str()),
|
|
sandbox_id.length());
|
|
metrics_->oemcrypto_set_sandbox_.Record(sandbox_id);
|
|
}
|
|
M_TIME(sts = OEMCrypto_InitializeAndCheckKeybox(
|
|
&needs_keybox_provisioning_),
|
|
metrics_, oemcrypto_initialize_, sts);
|
|
});
|
|
if (OEMCrypto_SUCCESS != sts) {
|
|
LOGE("OEMCrypto_Initialize failed: status = %d", static_cast<int>(sts));
|
|
return;
|
|
}
|
|
termination_counter_ = Properties::delay_oem_crypto_termination()
|
|
? kMaxTerminateCountDown
|
|
: 0;
|
|
initialized_ = true;
|
|
initialized = true;
|
|
}
|
|
});
|
|
|
|
if (initialized) {
|
|
CacheVersion();
|
|
}
|
|
}
|
|
|
|
void CryptoSession::ReinitializeForTest() {
|
|
if (initialized_) {
|
|
const OEMCryptoResult status = OEMCrypto_Terminate();
|
|
if (OEMCrypto_SUCCESS != status) {
|
|
LOGE("OEMCrypto_Terminate failed: %d", status);
|
|
return;
|
|
}
|
|
initialized_ = false;
|
|
// Tables will be reinitialized by tests when needed.
|
|
usage_table_l1_.reset();
|
|
usage_table_l3_.reset();
|
|
}
|
|
// Give up if we cannot initialize at all.
|
|
const OEMCryptoResult status = OEMCrypto_Initialize();
|
|
if (OEMCrypto_SUCCESS != status) {
|
|
LOGE("OEMCrypto_Initialize failed: %d", status);
|
|
return;
|
|
}
|
|
OEMCrypto_SetMaxAPIVersion(ODK_MAJOR_VERSION);
|
|
OEMCrypto_EnterTestMode();
|
|
initialized_ = true;
|
|
// For integration and unit tests we will install a test keybox and do not
|
|
// need to do keybox provisioning.
|
|
needs_keybox_provisioning_ = false;
|
|
// This was skipped in Init because initialization failed.
|
|
CacheVersion();
|
|
}
|
|
|
|
void CryptoSession::CacheVersion() {
|
|
uint32_t version;
|
|
std::string api_version =
|
|
CryptoSession::GetApiVersion(kLevelDefault, &version)
|
|
? std::to_string(version)
|
|
: kStringNotAvailable;
|
|
std::string api_minor_version =
|
|
CryptoSession::GetApiMinorVersion(kLevelDefault, &version)
|
|
? std::to_string(version)
|
|
: kStringNotAvailable;
|
|
LOGD("OEMCrypto version (default security level): %s.%s", api_version.c_str(),
|
|
api_minor_version.c_str());
|
|
|
|
api_version = CryptoSession::GetApiVersion(kLevel3, &version)
|
|
? std::to_string(version)
|
|
: kStringNotAvailable;
|
|
api_minor_version = CryptoSession::GetApiMinorVersion(kLevel3, &version)
|
|
? std::to_string(version)
|
|
: kStringNotAvailable;
|
|
LOGD("OEMCrypto version (L3 security level): %s.%s", api_version.c_str(),
|
|
api_minor_version.c_str());
|
|
if (needs_keybox_provisioning_) {
|
|
WithStaticFieldWriteLock("SystemFallbackPolicy", [&] {
|
|
if (!okp_fallback_policy_l1_) {
|
|
LOGD("OEMCrypto needs keybox provisioning");
|
|
// Only create once. Possible that OEMCrypto is initialized
|
|
// and terminated many times over the life cycle of the OTA
|
|
// keybox provisioning process.
|
|
okp_fallback_policy_l1_ = okp::SystemFallbackPolicy::Create();
|
|
if (okp_fallback_policy_l1_)
|
|
okp_fallback_policy_l1_->MarkNeedsProvisioning();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
bool CryptoSession::TryTerminate() {
|
|
LOGV("Terminating crypto session");
|
|
const bool terminated = WithStaticFieldWriteLock("TryTerminate", [&] {
|
|
LOGV(
|
|
"Terminating crypto session: initialized_ = %s, session_count_ = %d, "
|
|
"termination_counter_ = %d",
|
|
initialized_ ? "true" : "false", session_count_, termination_counter_);
|
|
|
|
if (termination_counter_ > 0) {
|
|
--termination_counter_;
|
|
}
|
|
if (session_count_ > 0 || termination_counter_ > 0 || !initialized_)
|
|
return false;
|
|
|
|
const OEMCryptoResult sts =
|
|
WithOecWriteLock("Terminate", [&] { return OEMCrypto_Terminate(); });
|
|
if (OEMCrypto_SUCCESS != sts) {
|
|
LOGE("OEMCrypto_Terminate failed: status = %d", static_cast<int>(sts));
|
|
}
|
|
initialized_ = false;
|
|
return true;
|
|
});
|
|
if (terminated) {
|
|
UsageTableLock lock(usage_table_mutex_);
|
|
usage_table_l1_.reset();
|
|
usage_table_l3_.reset();
|
|
}
|
|
return terminated;
|
|
}
|
|
|
|
void CryptoSession::DisableDelayedTermination() {
|
|
LOGV("Disable delayed termination");
|
|
WithStaticFieldWriteLock("DisableDelayedTermination",
|
|
[&] { termination_counter_ = 0; });
|
|
}
|
|
|
|
bool CryptoSession::SetUpUsageTable(
|
|
RequestedSecurityLevel requested_security_level) {
|
|
if (usage_table_ != nullptr) {
|
|
LOGE("Usage table is already set up for the current crypto session");
|
|
return false;
|
|
}
|
|
const CdmSecurityLevel security_level =
|
|
GetSecurityLevel(requested_security_level);
|
|
if (security_level != kSecurityLevelL1 &&
|
|
security_level != kSecurityLevelL3) {
|
|
LOGD("Unsupported security level for usage support: security_level = %d",
|
|
static_cast<int>(security_level));
|
|
return false;
|
|
}
|
|
|
|
// Check if usage support is available.
|
|
bool supports_usage_table = false;
|
|
if (!HasUsageTableSupport(requested_security_level, &supports_usage_table)) {
|
|
metrics_->oemcrypto_usage_table_support_.SetError(
|
|
USAGE_INFORMATION_SUPPORT_FAILED);
|
|
return false;
|
|
}
|
|
metrics_->oemcrypto_usage_table_support_.Record(
|
|
supports_usage_table ? kUsageEntrySupport : kNonSecureUsageSupport);
|
|
if (!supports_usage_table) {
|
|
return false;
|
|
}
|
|
|
|
LOGV("Usage table lock: SetUpUsageTable()");
|
|
UsageTableLock auto_lock(usage_table_mutex_);
|
|
// TODO(b/141350978): Prevent any recursive logic.
|
|
|
|
// Manipulate only the usage table for the requested security level.
|
|
std::unique_ptr<CdmUsageTable>& table =
|
|
security_level == kSecurityLevelL1 ? usage_table_l1_ : usage_table_l3_;
|
|
if (!table) {
|
|
// This may be called twice within the same thread when the table
|
|
// is initialized. On the second call |header| will not be null,
|
|
// causing this block to be skipped.
|
|
table.reset(new CdmUsageTable());
|
|
if (!table->Init(security_level, this)) {
|
|
LOGE("Failed to initialize and sync usage usage table");
|
|
// Must be cleared globally to prevent the next session to be
|
|
// opened from using the invalid CdmUsageTable.
|
|
table.reset();
|
|
return false;
|
|
}
|
|
}
|
|
usage_table_ = table.get();
|
|
metrics_->usage_table_header_initial_size_.Record(usage_table_->size());
|
|
return true;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetTokenFromKeybox(
|
|
RequestedSecurityLevel requested_security_level, std::string* key_data) {
|
|
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
|
|
RETURN_IF_NULL(key_data, PARAMETER_NULL);
|
|
LOGV("requested_security_level = %s",
|
|
RequestedSecurityLevelToString(requested_security_level));
|
|
// Devices with an invalid L1 keybox which support OTA keybox
|
|
// provisioning don't have keybox data.
|
|
const bool keybox_provisioning_required = WithStaticFieldReadLock(
|
|
"GetTokenFromKeybox - keybox_provisioning_required", [&] {
|
|
if (requested_security_level_ != kLevelDefault) return false;
|
|
return needs_keybox_provisioning_;
|
|
});
|
|
if (keybox_provisioning_required) return CdmResponseType(NEED_PROVISIONING);
|
|
|
|
size_t key_data_length = KEYBOX_KEY_DATA_SIZE;
|
|
key_data->assign(key_data_length, '\0');
|
|
OEMCryptoResult status;
|
|
WithOecReadLock("GetTokenFromKeybox", [&] {
|
|
M_TIME(status = OEMCrypto_GetKeyData(
|
|
reinterpret_cast<uint8_t*>(&key_data->front()), &key_data_length,
|
|
requested_security_level),
|
|
metrics_, oemcrypto_get_key_data_, status,
|
|
metrics::Pow2Bucket(key_data_length));
|
|
});
|
|
if (OEMCrypto_SUCCESS == status) {
|
|
key_data->resize(key_data_length);
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
key_data->clear();
|
|
return MapOEMCryptoResult(status, GET_TOKEN_FROM_KEYBOX_ERROR,
|
|
"GetTokenFromKeybox");
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetTokenFromOemCert(
|
|
RequestedSecurityLevel requested_security_level, std::string* oem_cert) {
|
|
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
|
|
RETURN_IF_NULL(oem_cert, PARAMETER_NULL);
|
|
LOGV("requested_security_level = %s",
|
|
RequestedSecurityLevelToString(requested_security_level));
|
|
|
|
const bool cache_success =
|
|
WithOecSessionLock("GetTokenFromOemCert - check cached", [&] {
|
|
if (oem_token_.empty()) {
|
|
return false;
|
|
}
|
|
oem_cert->assign(oem_token_);
|
|
return true;
|
|
});
|
|
if (cache_success) return CdmResponseType(NO_ERROR);
|
|
|
|
size_t oem_cert_length = CERTIFICATE_DATA_SIZE;
|
|
oem_cert->assign(oem_cert_length, '\0');
|
|
OEMCryptoResult status =
|
|
WithOecReadLock("GetTokenFromOemCert - attempt 1", [&] {
|
|
return OEMCrypto_GetOEMPublicCertificate(
|
|
reinterpret_cast<uint8_t*>(&oem_cert->front()), &oem_cert_length,
|
|
requested_security_level);
|
|
});
|
|
metrics_->oemcrypto_get_oem_public_certificate_.Increment(status);
|
|
if (status == OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
oem_cert->assign(oem_cert_length, '\0');
|
|
status = WithOecReadLock("GetTokenFromOemCert - attempt 2", [&] {
|
|
return OEMCrypto_GetOEMPublicCertificate(
|
|
reinterpret_cast<uint8_t*>(&oem_cert->front()), &oem_cert_length,
|
|
requested_security_level);
|
|
});
|
|
metrics_->oemcrypto_get_oem_public_certificate_.Increment(status);
|
|
}
|
|
|
|
if (status == OEMCrypto_SUCCESS) {
|
|
oem_cert->resize(oem_cert_length);
|
|
WithOecSessionLock("GetTokenFromOemCert - set cache",
|
|
[&] { oem_token_ = *oem_cert; });
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
oem_cert->clear();
|
|
return MapOEMCryptoResult(status, GET_TOKEN_FROM_OEM_CERT_ERROR,
|
|
"GetTokenFromOemCert");
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetProvisioningToken(
|
|
std::string* token, std::string* additional_token) {
|
|
RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN);
|
|
return GetProvisioningToken(requested_security_level_, token,
|
|
additional_token);
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetProvisioningToken(
|
|
RequestedSecurityLevel requested_security_level, std::string* token,
|
|
std::string* additional_token) {
|
|
if (token == nullptr || additional_token == nullptr) {
|
|
metrics_->crypto_session_get_token_.Increment(
|
|
CdmResponseType(PARAMETER_NULL));
|
|
RETURN_IF_NULL(token, PARAMETER_NULL);
|
|
RETURN_IF_NULL(additional_token, PARAMETER_NULL);
|
|
}
|
|
if (!IsInitialized()) {
|
|
metrics_->crypto_session_get_token_.Increment(
|
|
CdmResponseType(CRYPTO_SESSION_NOT_INITIALIZED));
|
|
return CdmResponseType(CRYPTO_SESSION_NOT_INITIALIZED);
|
|
}
|
|
|
|
CdmResponseType status(UNKNOWN_CLIENT_TOKEN_TYPE);
|
|
if (pre_provision_token_type_ == kClientTokenKeybox) {
|
|
status = GetTokenFromKeybox(requested_security_level, token);
|
|
} else if (pre_provision_token_type_ == kClientTokenOemCert) {
|
|
status = GetTokenFromOemCert(requested_security_level, token);
|
|
} else if (pre_provision_token_type_ == kClientTokenBootCertChain) {
|
|
status = GetBootCertificateChain(requested_security_level, token,
|
|
additional_token);
|
|
} else if (pre_provision_token_type_ ==
|
|
kClientTokenDrmCertificateReprovisioning) {
|
|
status = GetTokenFromEmbeddedCertificate(token);
|
|
}
|
|
metrics_->crypto_session_get_token_.Increment(status);
|
|
return status;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetProvisioning40TokenType(
|
|
OEMCrypto_BCCType* bcc_type) {
|
|
RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN);
|
|
return GetProvisioning40TokenType(requested_security_level_, bcc_type);
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetProvisioning40TokenType(
|
|
RequestedSecurityLevel requested_security_level,
|
|
OEMCrypto_BCCType* bcc_type) {
|
|
RETURN_IF_NULL(bcc_type, PARAMETER_NULL);
|
|
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
|
|
OEMCryptoResult sts = WithOecReadLock("GetBCCType", [&] {
|
|
return OEMCrypto_GetBCCType(requested_security_level, bcc_type);
|
|
});
|
|
return MapOEMCryptoResult(sts, UNKNOWN_CLIENT_TOKEN_TYPE,
|
|
"GetProvisioning40TokenType");
|
|
}
|
|
|
|
CdmSecurityLevel CryptoSession::GetSecurityLevel() {
|
|
LOGV("Getting security level");
|
|
RETURN_IF_NOT_OPEN(kSecurityLevelUninitialized);
|
|
return GetSecurityLevel(requested_security_level_);
|
|
}
|
|
|
|
CdmSecurityLevel CryptoSession::GetSecurityLevel(
|
|
RequestedSecurityLevel requested_security_level) {
|
|
LOGV("Getting security level: requested_security_level = %s",
|
|
RequestedSecurityLevelToString(requested_security_level));
|
|
RETURN_IF_UNINITIALIZED(kSecurityLevelUninitialized);
|
|
const OEMCrypto_Security_Level level = WithOecReadLock(
|
|
"GetSecurityLevel",
|
|
[&] { return OEMCrypto_SecurityLevel(requested_security_level); });
|
|
if (level == 0) {
|
|
LOGE("Security level is unknown: requested_security_level = %d",
|
|
static_cast<int>(requested_security_level));
|
|
return kSecurityLevelUnknown;
|
|
}
|
|
if (level == OEMCrypto_Level1) {
|
|
return kSecurityLevelL1;
|
|
}
|
|
if (level == OEMCrypto_Level2) {
|
|
return kSecurityLevelL2;
|
|
}
|
|
if (level == OEMCrypto_Level3) {
|
|
return kSecurityLevelL3;
|
|
}
|
|
LOGE(
|
|
"Ill-formed security level: "
|
|
"level = \"L%u\", requested_security_level = %s",
|
|
static_cast<unsigned int>(level),
|
|
RequestedSecurityLevelToString(requested_security_level));
|
|
return kSecurityLevelUnknown;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetInternalDeviceUniqueId(
|
|
std::string* device_id) {
|
|
RETURN_IF_NULL(device_id, PARAMETER_NULL);
|
|
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
|
|
|
|
size_t device_id_length = 64;
|
|
device_id->assign(device_id_length, '\0');
|
|
|
|
OEMCryptoResult sts =
|
|
WithOecReadLock("GetInternalDeviceUniqueId Attempt 1", [&] {
|
|
return OEMCrypto_GetDeviceID(
|
|
reinterpret_cast<uint8_t*>(&device_id->front()), &device_id_length,
|
|
requested_security_level_);
|
|
});
|
|
metrics_->oemcrypto_get_device_id_.Increment(sts);
|
|
|
|
if (sts == OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
device_id->resize(device_id_length, '\0');
|
|
sts = WithOecReadLock("GetInternalDeviceUniqueId Attempt 2", [&] {
|
|
return OEMCrypto_GetDeviceID(
|
|
reinterpret_cast<uint8_t*>(&device_id->front()), &device_id_length,
|
|
requested_security_level_);
|
|
});
|
|
metrics_->oemcrypto_get_device_id_.Increment(sts);
|
|
}
|
|
|
|
// Either the authentication root is a keybox or the device has transitioned
|
|
// to using OEMCerts.
|
|
// OEMCryptos, like the Level 3, that transition from Provisioning 2.0 to
|
|
// 3.0 would have a new device ID, which would affect SPOID calculation.
|
|
// In order to resolve this, we use OEMCrypto_GetDeviceID if it is
|
|
// implemented, so the OEMCrypto can continue to report the same device ID.
|
|
if (sts == OEMCrypto_SUCCESS) {
|
|
device_id->resize(device_id_length);
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
device_id->clear();
|
|
|
|
if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED &&
|
|
pre_provision_token_type_ == kClientTokenOemCert) {
|
|
return GetTokenFromOemCert(requested_security_level_, device_id);
|
|
}
|
|
|
|
const bool use_null_device_id = WithStaticFieldReadLock(
|
|
"GetInternalDeviceUniqueId() use_null_device_id", [&] {
|
|
if (requested_security_level_ != kLevelDefault) return false;
|
|
if (!needs_keybox_provisioning_) return false;
|
|
if (sts != OEMCrypto_ERROR_KEYBOX_INVALID &&
|
|
sts != OEMCrypto_ERROR_NO_DEVICEID) {
|
|
// Logging other error for debugging, but null device
|
|
// ID should still be returned.
|
|
LOGE("Unexpected error: sts = %d", sts);
|
|
}
|
|
return true;
|
|
});
|
|
if (use_null_device_id) {
|
|
LOGD("Using null device ID");
|
|
constexpr size_t kKeyboxDeviceIdLength = 32;
|
|
device_id->assign(kKeyboxDeviceIdLength, '\0');
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
|
|
return MapOEMCryptoResult(sts, GET_DEVICE_ID_ERROR,
|
|
"GetInternalDeviceUniqueId");
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetExternalDeviceUniqueId(
|
|
std::string* device_id) {
|
|
RETURN_IF_NULL(device_id, PARAMETER_NULL);
|
|
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
|
|
const CdmResponseType status = GetInternalDeviceUniqueId(device_id);
|
|
if (status != NO_ERROR) return status;
|
|
if (device_id->size() > kMaxExternalDeviceIdLength) {
|
|
// To keep the size of the value passed back to the application down, hash
|
|
// the large OEM Public Cert to a smaller value.
|
|
*device_id = Sha256Hash(*device_id);
|
|
}
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
|
|
bool CryptoSession::GetApiVersion(uint32_t* version) {
|
|
LOGV("Getting API version");
|
|
RETURN_IF_NOT_OPEN(false);
|
|
return GetApiVersion(requested_security_level_, version);
|
|
}
|
|
|
|
bool CryptoSession::GetApiVersion(RequestedSecurityLevel security_level,
|
|
uint32_t* version) {
|
|
LOGV("Getting API version: security_level = %s",
|
|
RequestedSecurityLevelToString(security_level));
|
|
if (!version) {
|
|
LOGE("Output parameter |version| not provided");
|
|
return false;
|
|
}
|
|
RETURN_IF_UNINITIALIZED(false);
|
|
|
|
WithOecReadLock("GetApiVersion",
|
|
[&] { *version = OEMCrypto_APIVersion(security_level); });
|
|
// Record the version into the metrics.
|
|
metrics_->oemcrypto_api_version_.Record(*version);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CryptoSession::GetApiMinorVersion(RequestedSecurityLevel security_level,
|
|
uint32_t* minor_version) {
|
|
LOGV("Getting API minor version: security_level = %s",
|
|
RequestedSecurityLevelToString(security_level));
|
|
if (!minor_version) {
|
|
LOGE("Output parameter |minor_version| not provided");
|
|
return false;
|
|
}
|
|
RETURN_IF_UNINITIALIZED(false);
|
|
|
|
WithOecReadLock("GetApiMinorVersion", [&] {
|
|
*minor_version = OEMCrypto_MinorAPIVersion(security_level);
|
|
});
|
|
// Record the minor version into the metrics.
|
|
metrics_->oemcrypto_minor_api_version_.Record(*minor_version);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CryptoSession::GetCachedSystemId(uint32_t* system_id) {
|
|
RETURN_IF_NULL(system_id, false);
|
|
RETURN_IF_NOT_OPEN(false);
|
|
if (system_id_ == NULL_SYSTEM_ID) return false;
|
|
*system_id = system_id_;
|
|
return true;
|
|
}
|
|
|
|
void CryptoSession::SetSystemId(uint32_t system_id) {
|
|
if (!IsOpen()) return; // Ignore silently.
|
|
system_id_ = system_id;
|
|
metrics_->crypto_session_system_id_.Record(system_id_);
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetProvisioningId(std::string* provisioning_id) {
|
|
RETURN_IF_NULL(provisioning_id, PARAMETER_NULL);
|
|
RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN);
|
|
|
|
if (pre_provision_token_type_ == kClientTokenOemCert ||
|
|
pre_provision_token_type_ == kClientTokenBootCertChain) {
|
|
// OEM Cert and BCC devices have no provisioning-unique ID embedded in
|
|
// them, so we synthesize one by using the External Device-Unique ID
|
|
// and inverting all the bits.
|
|
const CdmResponseType status = GetExternalDeviceUniqueId(provisioning_id);
|
|
if (status != NO_ERROR) return status;
|
|
|
|
for (char& c : *provisioning_id) {
|
|
c ^= 0xff;
|
|
}
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
if (pre_provision_token_type_ == kClientTokenKeybox) {
|
|
std::string token;
|
|
CdmResponseType status =
|
|
GetTokenFromKeybox(requested_security_level_, &token);
|
|
|
|
if (status != NO_ERROR) return status;
|
|
|
|
if (token.size() < 24) {
|
|
LOGE("Keybox token size too small: %zu", token.size());
|
|
return CdmResponseType(KEYBOX_TOKEN_TOO_SHORT);
|
|
}
|
|
|
|
provisioning_id->assign(reinterpret_cast<char*>(&token[8]), 16);
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
LOGE("Unsupported pre-provision token type: %d",
|
|
static_cast<int>(pre_provision_token_type_));
|
|
return CdmResponseType(UNKNOWN_CLIENT_TOKEN_TYPE);
|
|
}
|
|
|
|
uint8_t CryptoSession::GetSecurityPatchLevel() {
|
|
uint8_t patch;
|
|
WithOecReadLock("GetSecurityPatchLevel", [&] {
|
|
patch = OEMCrypto_Security_Patch_Level(requested_security_level_);
|
|
});
|
|
metrics_->oemcrypto_security_patch_level_.Record(patch);
|
|
return patch;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::Open(
|
|
RequestedSecurityLevel requested_security_level) {
|
|
LOGD("Opening crypto session: requested_security_level = %s",
|
|
RequestedSecurityLevelToString(requested_security_level));
|
|
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
|
|
if (open_) return CdmResponseType(NO_ERROR);
|
|
|
|
if (!SetUpUsageTable(requested_security_level)) {
|
|
// Ignore errors since we do not know when a session is opened,
|
|
// if it is intended to be used for offline/usage session related
|
|
// or otherwise.
|
|
LOGW("Session opened without a usage table");
|
|
}
|
|
|
|
CdmResponseType result = GetProvisioningMethod(requested_security_level,
|
|
&pre_provision_token_type_);
|
|
if (result != NO_ERROR) return result;
|
|
|
|
OEMCrypto_SESSION sid;
|
|
requested_security_level_ = requested_security_level;
|
|
OEMCryptoResult sts;
|
|
WithOecWriteLock("Open() calling OEMCrypto_OpenSession", [&] {
|
|
sts = OEMCrypto_OpenSession(&sid, requested_security_level);
|
|
});
|
|
|
|
if (sts != OEMCrypto_SUCCESS) {
|
|
WithStaticFieldReadLock(
|
|
"Open() reporting OEMCrypto_OpenSession Failure", [&] {
|
|
LOGE(
|
|
"OEMCrypto_Open failed: status = %d, session_count_ = %d,"
|
|
" initialized_ = %s",
|
|
static_cast<int>(sts), session_count_,
|
|
initialized_ ? "true" : "false");
|
|
});
|
|
return MapOEMCryptoResult(sts, OPEN_CRYPTO_SESSION_ERROR, "Open");
|
|
}
|
|
|
|
WithStaticFieldWriteLock("Open() termination_counter", [&] {
|
|
termination_counter_ =
|
|
Properties::delay_oem_crypto_termination() ? kMaxTerminateCountDown : 0;
|
|
});
|
|
|
|
oec_session_id_ = static_cast<CryptoSessionId>(sid);
|
|
LOGV("Opened session: id = %u", oec_session_id_);
|
|
open_ = true;
|
|
|
|
// Set up request ID
|
|
uint64_t request_id_base =
|
|
wvutil::CdmRandom::RandomInRange(std::numeric_limits<uint64_t>::max());
|
|
uint64_t request_id_index =
|
|
request_id_index_source_.fetch_add(1, std::memory_order_relaxed);
|
|
request_id_ = wvutil::HexEncode(reinterpret_cast<uint8_t*>(&request_id_base),
|
|
sizeof(request_id_base)) +
|
|
wvutil::HexEncode(reinterpret_cast<uint8_t*>(&request_id_index),
|
|
sizeof(request_id_index));
|
|
|
|
// Initialize key session
|
|
WithOecSessionLock("Open() calling key_session_.reset()", [&] {
|
|
key_session_.reset(new ContentKeySession(requested_security_level_,
|
|
oec_session_id_, metrics_));
|
|
});
|
|
|
|
if (!GetApiVersion(&api_version_)) {
|
|
LOGE("Failed to get API version");
|
|
return CdmResponseType(USAGE_SUPPORT_GET_API_FAILED);
|
|
}
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
|
|
void CryptoSession::Close() {
|
|
LOGV("Closing crypto session: id = %u, open = %s", oec_session_id_,
|
|
open_ ? "true" : "false");
|
|
if (!open_) return;
|
|
|
|
const OEMCryptoResult close_sts = WithOecWriteLock(
|
|
"Close", [&] { return OEMCrypto_CloseSession(oec_session_id_); });
|
|
metrics_->oemcrypto_close_session_.Increment(close_sts);
|
|
|
|
// Clear cached values.
|
|
has_usage_table_support_ = kBooleanUnset;
|
|
oem_token_.clear();
|
|
system_id_ = NULL_SYSTEM_ID;
|
|
pre_provision_token_type_ = kClientTokenUninitialized;
|
|
|
|
if (close_sts != OEMCrypto_SUCCESS) {
|
|
LOGW("OEMCrypto_CloseSession failed: status = %d",
|
|
static_cast<int>(close_sts));
|
|
}
|
|
switch (close_sts) {
|
|
case OEMCrypto_SUCCESS:
|
|
case OEMCrypto_ERROR_INVALID_SESSION:
|
|
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
|
|
usage_table_ = nullptr;
|
|
open_ = false;
|
|
break;
|
|
case OEMCrypto_ERROR_CLOSE_SESSION_FAILED:
|
|
default:
|
|
// empty case
|
|
break;
|
|
}
|
|
}
|
|
|
|
CdmResponseType CryptoSession::PrepareAndSignLicenseRequest(
|
|
const std::string& message, std::string* core_message,
|
|
std::string* signature, bool& should_specify_algorithm,
|
|
OEMCrypto_SignatureHashAlgorithm& algorithm) {
|
|
LOGV("Preparing and signing license request: id = %u", oec_session_id_);
|
|
RETURN_IF_NULL(signature, PARAMETER_NULL);
|
|
RETURN_IF_NULL(core_message, PARAMETER_NULL);
|
|
RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN);
|
|
|
|
OEMCryptoResult sts;
|
|
WithOecSessionLock("GetSignatureHashAlgorithm", [&] {
|
|
sts = OEMCrypto_GetSignatureHashAlgorithm(oec_session_id_, &algorithm);
|
|
});
|
|
metrics_->oemcrypto_get_signature_hash_algorithm_.Increment(sts, algorithm);
|
|
if (sts == OEMCrypto_SUCCESS) {
|
|
should_specify_algorithm = true;
|
|
} else if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) {
|
|
should_specify_algorithm = false;
|
|
} else {
|
|
return MapOEMCryptoResult(sts, GET_SIGNATURE_HASH_ALGORITHM_ERROR_1,
|
|
"PrepareAndSignLicenseRequest");
|
|
}
|
|
|
|
size_t signature_length = 0;
|
|
size_t core_message_length = 0;
|
|
*core_message = "";
|
|
std::string combined_message = *core_message + message;
|
|
// First call is intended to determine the required size of the
|
|
// output buffers.
|
|
WithOecSessionLock("PrepareAndSignLicenseRequest", [&] {
|
|
M_TIME(sts = OEMCrypto_PrepAndSignLicenseRequest(
|
|
oec_session_id_,
|
|
reinterpret_cast<uint8_t*>(
|
|
const_cast<char*>(combined_message.data())),
|
|
combined_message.size(), &core_message_length, nullptr,
|
|
&signature_length),
|
|
metrics_, oemcrypto_prep_and_sign_license_request_, sts);
|
|
});
|
|
|
|
if (OEMCrypto_ERROR_SHORT_BUFFER != sts) {
|
|
// TODO(b/174412779): Remove when b/170704368 is fixed.
|
|
// Temporary workaround. If this error is returned the only way to
|
|
// recover is for the app to reprovision.
|
|
if (static_cast<int>(sts) == kRsaSsaPssSignatureLengthError) {
|
|
LOGE("OEMCrypto PrepareAndSignLicenseRequest result = %d",
|
|
static_cast<int>(sts));
|
|
return CRYPTO_ERROR(NEED_PROVISIONING, sts);
|
|
}
|
|
return MapOEMCryptoResult(sts, GENERATE_SIGNATURE_ERROR,
|
|
"PrepareAndSignLicenseRequest");
|
|
}
|
|
|
|
// Resize.
|
|
core_message->resize(core_message_length);
|
|
signature->resize(signature_length);
|
|
|
|
combined_message = *core_message + message;
|
|
WithOecSessionLock("PrepareAndSignLicenseRequest", [&] {
|
|
M_TIME(sts = OEMCrypto_PrepAndSignLicenseRequest(
|
|
oec_session_id_,
|
|
reinterpret_cast<uint8_t*>(
|
|
const_cast<char*>(combined_message.data())),
|
|
combined_message.size(), &core_message_length,
|
|
reinterpret_cast<uint8_t*>(const_cast<char*>(signature->data())),
|
|
&signature_length),
|
|
metrics_, oemcrypto_prep_and_sign_license_request_, sts);
|
|
});
|
|
|
|
if (OEMCrypto_SUCCESS == sts) {
|
|
signature->resize(signature_length);
|
|
*core_message = std::move(combined_message);
|
|
// Truncate combined message to only contain the core message.
|
|
core_message->resize(core_message_length);
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
// TODO(b/174412779): Remove when b/170704368 is fixed.
|
|
// Temporary workaround. If this error is returned the only way to
|
|
// recover is for the app to reprovision.
|
|
if (static_cast<int>(sts) == kRsaSsaPssSignatureLengthError) {
|
|
LOGE("OEMCrypto PrepareAndSignLicenseRequest result = %d",
|
|
static_cast<int>(sts));
|
|
return CRYPTO_ERROR(NEED_PROVISIONING, sts);
|
|
}
|
|
return MapOEMCryptoResult(sts, GENERATE_SIGNATURE_ERROR,
|
|
"PrepareAndSignLicenseRequest");
|
|
}
|
|
|
|
#ifdef HAS_DUAL_KEY
|
|
CdmResponseType CryptoSession::UseSecondaryKey(bool dual_key) {
|
|
OEMCryptoResult sts;
|
|
WithOecSessionLock("UseSecondaryKey", [&] {
|
|
sts = OEMCrypto_UseSecondaryKey(oec_session_id_, dual_key);
|
|
});
|
|
|
|
return MapOEMCryptoResult(sts, LOAD_KEY_ERROR, "UseSecondaryKey");
|
|
}
|
|
#else
|
|
CdmResponseType CryptoSession::UseSecondaryKey(bool /* dual_key */) {
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
#endif
|
|
|
|
CdmResponseType CryptoSession::LoadLicense(const std::string& context,
|
|
const std::string& session_key,
|
|
const std::string& signed_message,
|
|
const std::string& core_message,
|
|
const std::string& signature,
|
|
CdmLicenseKeyType key_type) {
|
|
LOGV("Loading license: id = %u", oec_session_id_);
|
|
const std::string combined_message = core_message + signed_message;
|
|
OEMCryptoResult sts;
|
|
WithOecSessionLock("LoadLicense", [&] {
|
|
if (key_type == kLicenseKeyTypeEntitlement &&
|
|
key_session_->Type() != KeySession::kEntitlement) {
|
|
key_session_.reset(new EntitlementKeySession(requested_security_level_,
|
|
oec_session_id_, metrics_));
|
|
}
|
|
|
|
M_TIME(sts = OEMCrypto_LoadLicense(
|
|
oec_session_id_,
|
|
reinterpret_cast<const uint8_t*>(context.data()), context.size(),
|
|
reinterpret_cast<const uint8_t*>(session_key.data()),
|
|
session_key.size(),
|
|
reinterpret_cast<const uint8_t*>(combined_message.data()),
|
|
combined_message.size(), core_message.size(),
|
|
reinterpret_cast<const uint8_t*>(signature.data()),
|
|
signature.size()),
|
|
metrics_, oemcrypto_load_license_, sts);
|
|
});
|
|
|
|
switch (sts) {
|
|
case OEMCrypto_SUCCESS:
|
|
return CdmResponseType(KEY_ADDED);
|
|
case OEMCrypto_ERROR_BUFFER_TOO_LARGE:
|
|
LOGE("LoadLicense buffer too large: size = %zu", combined_message.size());
|
|
return CRYPTO_ERROR(LOAD_LICENSE_ERROR, sts);
|
|
case OEMCrypto_ERROR_TOO_MANY_KEYS:
|
|
LOGE("Too many keys in license");
|
|
return CRYPTO_ERROR(INSUFFICIENT_CRYPTO_RESOURCES, sts);
|
|
default:
|
|
break;
|
|
}
|
|
return MapOEMCryptoResult(sts, LOAD_LICENSE_ERROR, "LoadLicense");
|
|
}
|
|
|
|
CdmResponseType CryptoSession::PrepareAndSignRenewalRequest(
|
|
const std::string& message, std::string* core_message,
|
|
std::string* signature) {
|
|
LOGV("Preparing and signing renewal request: id = %u", oec_session_id_);
|
|
if (signature == nullptr) {
|
|
LOGE("Output parameter |signature| not provided");
|
|
return CdmResponseType(PARAMETER_NULL);
|
|
}
|
|
if (core_message == nullptr) {
|
|
LOGE("Output parameter |core_message| not provided");
|
|
return CdmResponseType(PARAMETER_NULL);
|
|
}
|
|
|
|
OEMCryptoResult sts;
|
|
size_t signature_length = 0;
|
|
size_t core_message_length = 0;
|
|
*core_message = "";
|
|
std::string combined_message = *core_message + message;
|
|
// First call is intended to determine the required size of the
|
|
// output buffers.
|
|
WithOecSessionLock("PrepareAndSignRenewalRequest", [&] {
|
|
M_TIME(sts = OEMCrypto_PrepAndSignRenewalRequest(
|
|
oec_session_id_,
|
|
reinterpret_cast<uint8_t*>(
|
|
const_cast<char*>(combined_message.data())),
|
|
combined_message.size(), &core_message_length, nullptr,
|
|
&signature_length),
|
|
metrics_, oemcrypto_prep_and_sign_renewal_request_, sts);
|
|
});
|
|
|
|
if (OEMCrypto_ERROR_SHORT_BUFFER != sts) {
|
|
return MapOEMCryptoResult(sts, GENERATE_SIGNATURE_ERROR,
|
|
"PrepareAndSignRenewalRequest");
|
|
}
|
|
|
|
// Resize.
|
|
core_message->resize(core_message_length);
|
|
signature->resize(signature_length);
|
|
|
|
combined_message = *core_message + message;
|
|
WithOecSessionLock("PrepareAndSignRenewalRequest", [&] {
|
|
M_TIME(sts = OEMCrypto_PrepAndSignRenewalRequest(
|
|
oec_session_id_,
|
|
reinterpret_cast<uint8_t*>(
|
|
const_cast<char*>(combined_message.data())),
|
|
combined_message.size(), &core_message_length,
|
|
reinterpret_cast<uint8_t*>(const_cast<char*>(signature->data())),
|
|
&signature_length),
|
|
metrics_, oemcrypto_prep_and_sign_renewal_request_, sts);
|
|
});
|
|
|
|
if (OEMCrypto_SUCCESS == sts) {
|
|
signature->resize(signature_length);
|
|
*core_message = std::move(combined_message);
|
|
// Truncate combined message to only contain the core message.
|
|
core_message->resize(core_message_length);
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
return MapOEMCryptoResult(sts, GENERATE_SIGNATURE_ERROR,
|
|
"PrepareAndSignRenewalRequest");
|
|
}
|
|
|
|
CdmResponseType CryptoSession::LoadRenewal(const std::string& signed_message,
|
|
const std::string& core_message,
|
|
const std::string& signature) {
|
|
LOGV("Loading license renewal: id = %u", oec_session_id_);
|
|
const std::string combined_message = core_message + signed_message;
|
|
OEMCryptoResult sts;
|
|
WithOecSessionLock("LoadRenewal", [&] {
|
|
M_TIME(sts = OEMCrypto_LoadRenewal(
|
|
oec_session_id_,
|
|
reinterpret_cast<const uint8_t*>(combined_message.data()),
|
|
combined_message.size(), core_message.size(),
|
|
reinterpret_cast<const uint8_t*>(signature.data()),
|
|
signature.size()),
|
|
metrics_, oemcrypto_load_renewal_, sts);
|
|
});
|
|
if (sts == OEMCrypto_SUCCESS) {
|
|
return CdmResponseType(KEY_ADDED);
|
|
}
|
|
if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE) {
|
|
LOGE("Buffer too large: size = %zu", combined_message.size());
|
|
return CRYPTO_ERROR(LOAD_RENEWAL_ERROR, sts);
|
|
}
|
|
return MapOEMCryptoResult(sts, LOAD_RENEWAL_ERROR, "LoadRenewal");
|
|
}
|
|
|
|
CdmResponseType CryptoSession::PrepareAndSignProvisioningRequest(
|
|
const std::string& message, std::string* core_message,
|
|
std::string* signature, bool& should_specify_algorithm,
|
|
OEMCrypto_SignatureHashAlgorithm& algorithm) {
|
|
LOGV("Preparing and signing provisioning request: id = %u", oec_session_id_);
|
|
if (signature == nullptr) {
|
|
LOGE("Output parameter |signature| not provided");
|
|
return CdmResponseType(PARAMETER_NULL);
|
|
}
|
|
if (core_message == nullptr) {
|
|
LOGE("Output parameter |core_message| not provided");
|
|
return CdmResponseType(PARAMETER_NULL);
|
|
}
|
|
|
|
OEMCryptoResult sts;
|
|
// TODO: b/305093063 - Refactor to a switch statement to improve readability
|
|
if (pre_provision_token_type_ == kClientTokenKeybox) {
|
|
should_specify_algorithm = false;
|
|
} else if (pre_provision_token_type_ == kClientTokenOemCert) {
|
|
should_specify_algorithm = true;
|
|
WithOecSessionLock("LoadOEMPrivateKey", [&] {
|
|
sts = OEMCrypto_LoadOEMPrivateKey(oec_session_id_);
|
|
});
|
|
if (sts != OEMCrypto_SUCCESS) {
|
|
return MapOEMCryptoResult(sts, GET_TOKEN_FROM_OEM_CERT_ERROR,
|
|
"PrepareAndSignProvisioningRequest");
|
|
}
|
|
} else if (pre_provision_token_type_ == kClientTokenBootCertChain) {
|
|
should_specify_algorithm = true;
|
|
// Do nothing here. The key to signing the provisioning 4.0 request for each
|
|
// stage has been loaded already when it was generated by OEMCrypto.
|
|
} else if (pre_provision_token_type_ ==
|
|
kClientTokenDrmCertificateReprovisioning) {
|
|
should_specify_algorithm = false;
|
|
// Do nothing here. The baked-in certificate used as the token has already
|
|
// been loaded when the EncryptedClientId was filled in.
|
|
} else {
|
|
LOGE("Unknown method %d", pre_provision_token_type_);
|
|
return CdmResponseType(UNKNOWN_CLIENT_TOKEN_TYPE);
|
|
}
|
|
|
|
if (should_specify_algorithm) {
|
|
WithOecSessionLock("GetSignatureHashAlgorithm", [&] {
|
|
sts = OEMCrypto_GetSignatureHashAlgorithm(oec_session_id_, &algorithm);
|
|
});
|
|
metrics_->oemcrypto_get_signature_hash_algorithm_.Increment(sts, algorithm);
|
|
if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) {
|
|
should_specify_algorithm = false;
|
|
} else if (sts != OEMCrypto_SUCCESS) {
|
|
return MapOEMCryptoResult(sts, GET_SIGNATURE_HASH_ALGORITHM_ERROR_3,
|
|
"PrepareAndSignProvisioningRequest");
|
|
}
|
|
}
|
|
|
|
size_t signature_length = 0;
|
|
size_t core_message_length = 0;
|
|
*core_message = "";
|
|
std::string combined_message = *core_message + message;
|
|
// First call is intended to determine the required size of the
|
|
// output buffers.
|
|
WithOecSessionLock("PrepareAndSignProvisioningRequest", [&] {
|
|
M_TIME(sts = OEMCrypto_PrepAndSignProvisioningRequest(
|
|
oec_session_id_,
|
|
reinterpret_cast<uint8_t*>(
|
|
const_cast<char*>(combined_message.data())),
|
|
combined_message.size(), &core_message_length, nullptr,
|
|
&signature_length),
|
|
metrics_, oemcrypto_prep_and_sign_provisioning_request_, sts);
|
|
});
|
|
|
|
if (OEMCrypto_ERROR_SHORT_BUFFER != sts) {
|
|
return MapOEMCryptoResult(sts, GENERATE_SIGNATURE_ERROR,
|
|
"PrepareAndSignProvisioningRequest");
|
|
}
|
|
|
|
// Resize.
|
|
core_message->resize(core_message_length);
|
|
signature->resize(signature_length);
|
|
|
|
combined_message = *core_message + message;
|
|
WithOecSessionLock("PrepareAndSignProvisioningRequest", [&] {
|
|
M_TIME(sts = OEMCrypto_PrepAndSignProvisioningRequest(
|
|
oec_session_id_,
|
|
reinterpret_cast<uint8_t*>(
|
|
const_cast<char*>(combined_message.data())),
|
|
combined_message.size(), &core_message_length,
|
|
reinterpret_cast<uint8_t*>(const_cast<char*>(signature->data())),
|
|
&signature_length),
|
|
metrics_, oemcrypto_prep_and_sign_provisioning_request_, sts);
|
|
});
|
|
|
|
if (OEMCrypto_SUCCESS == sts) {
|
|
signature->resize(signature_length);
|
|
*core_message = std::move(combined_message);
|
|
// Truncate combined message to only contain the core message.
|
|
core_message->resize(core_message_length);
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
return MapOEMCryptoResult(sts, GENERATE_SIGNATURE_ERROR,
|
|
"PrepareAndSignProvisioningRequest");
|
|
}
|
|
|
|
CdmResponseType CryptoSession::LoadEntitledContentKeys(
|
|
const std::vector<CryptoKey>& key_array) {
|
|
const OEMCryptoResult sts = WithOecSessionLock(
|
|
"LoadEntitledContentKeys",
|
|
[&] { return key_session_->LoadEntitledContentKeys(key_array); });
|
|
|
|
switch (sts) {
|
|
case OEMCrypto_SUCCESS:
|
|
return CdmResponseType(KEY_ADDED);
|
|
case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES:
|
|
return CRYPTO_ERROR(INSUFFICIENT_CRYPTO_RESOURCES, sts);
|
|
case OEMCrypto_ERROR_INVALID_CONTEXT:
|
|
return CRYPTO_ERROR(NOT_AN_ENTITLEMENT_SESSION, sts);
|
|
case OEMCrypto_KEY_NOT_ENTITLED:
|
|
return CRYPTO_ERROR(NO_MATCHING_ENTITLEMENT_KEY, sts);
|
|
case OEMCrypto_ERROR_SESSION_LOST_STATE:
|
|
return CRYPTO_ERROR(SESSION_LOST_STATE_ERROR, sts);
|
|
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
|
|
return CRYPTO_ERROR(SYSTEM_INVALIDATED_ERROR, sts);
|
|
default:
|
|
return CRYPTO_ERROR(LOAD_ENTITLED_CONTENT_KEYS_ERROR, sts);
|
|
}
|
|
}
|
|
|
|
CdmResponseType CryptoSession::LoadCertificatePrivateKey(
|
|
const CryptoWrappedKey& private_key) {
|
|
const OEMCrypto_PrivateKeyType key_type =
|
|
(private_key.type() == CryptoWrappedKey::kEcc)
|
|
? OEMCrypto_ECC_Private_Key
|
|
: OEMCrypto_RSA_Private_Key;
|
|
const std::string& wrapped_key = private_key.key();
|
|
|
|
LOGV("Loading device DRM key: id = %u", oec_session_id_);
|
|
// TODO(b/140813486): determine if cert is RSA or ECC.
|
|
OEMCryptoResult sts;
|
|
WithOecSessionLock(
|
|
"LoadCertificatePrivateKey() calling OEMCrypto_LoadDRMPrivateKey()", [&] {
|
|
M_TIME(sts = OEMCrypto_LoadDRMPrivateKey(
|
|
oec_session_id_, key_type,
|
|
reinterpret_cast<const uint8_t*>(wrapped_key.data()),
|
|
wrapped_key.size()),
|
|
metrics_, oemcrypto_load_device_drm_key_, sts);
|
|
});
|
|
|
|
return MapOEMCryptoResult(sts, LOAD_DEVICE_RSA_KEY_ERROR,
|
|
"LoadCertificatePrivateKey");
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetBootCertificateChain(
|
|
std::string* bcc, std::string* additional_signature) {
|
|
RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN);
|
|
return GetBootCertificateChain(requested_security_level_, bcc,
|
|
additional_signature);
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetBootCertificateChain(
|
|
RequestedSecurityLevel requested_security_level, std::string* bcc,
|
|
std::string* additional_signature) {
|
|
RETURN_IF_NULL(bcc, PARAMETER_NULL);
|
|
RETURN_IF_NULL(additional_signature, PARAMETER_NULL);
|
|
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
|
|
LOGV("requested_security_level = %s",
|
|
RequestedSecurityLevelToString(requested_security_level));
|
|
CdmClientTokenType token_type = kClientTokenUninitialized;
|
|
const CdmResponseType status =
|
|
GetProvisioningMethod(requested_security_level, &token_type);
|
|
if (status != NO_ERROR) {
|
|
LOGE("Failed to get token type");
|
|
return status;
|
|
}
|
|
if (token_type != kClientTokenBootCertChain) {
|
|
return CdmResponseType(
|
|
PROVISIONING_TYPE_IS_NOT_BOOT_CERTIFICATE_CHAIN_ERROR);
|
|
}
|
|
if (requested_security_level != kLevelDefault) {
|
|
LOGE("CDM only supports L1 BCC");
|
|
return CdmResponseType(NOT_IMPLEMENTED_ERROR);
|
|
}
|
|
|
|
size_t bcc_length = 0;
|
|
size_t additional_signature_length = 0;
|
|
OEMCryptoResult sts =
|
|
WithOecReadLock("GetBootCertificateChain Attempt 1", [&] {
|
|
return OEMCrypto_GetBootCertificateChain(nullptr, &bcc_length, nullptr,
|
|
&additional_signature_length);
|
|
});
|
|
if (sts == OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
bcc->resize(bcc_length);
|
|
additional_signature->resize(additional_signature_length);
|
|
sts = WithOecReadLock("GetBootCertificateChain Attempt 2", [&] {
|
|
return OEMCrypto_GetBootCertificateChain(
|
|
MutableStringDataPointer(bcc), &bcc_length,
|
|
MutableStringDataPointer(additional_signature),
|
|
&additional_signature_length);
|
|
});
|
|
}
|
|
if (sts != OEMCrypto_SUCCESS) {
|
|
return MapOEMCryptoResult(sts, GET_BOOT_CERTIFICATE_CHAIN_ERROR,
|
|
"GetBootCertificateChain");
|
|
}
|
|
bcc->resize(bcc_length);
|
|
additional_signature->resize(additional_signature_length);
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetTokenFromEmbeddedCertificate(
|
|
std::string* token) {
|
|
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
|
|
RETURN_IF_NULL(token, PARAMETER_NULL);
|
|
|
|
CdmClientTokenType token_type = kClientTokenUninitialized;
|
|
const CdmResponseType sts =
|
|
GetProvisioningMethod(requested_security_level_, &token_type);
|
|
if (sts != NO_ERROR) {
|
|
LOGE("Failed to get token type");
|
|
return sts;
|
|
}
|
|
if (token_type != kClientTokenDrmCertificateReprovisioning) {
|
|
token->clear();
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
|
|
size_t certificate_length = CERTIFICATE_DATA_SIZE;
|
|
std::string embedded_certificate(certificate_length, '\0');
|
|
OEMCryptoResult status =
|
|
WithOecReadLock("GetTokenFromEmbeddedCertificate - attempt 1", [&] {
|
|
return OEMCrypto_GetEmbeddedDrmCertificate(
|
|
reinterpret_cast<uint8_t*>(&embedded_certificate.front()),
|
|
&certificate_length);
|
|
});
|
|
|
|
if (status == OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
embedded_certificate.assign(certificate_length, '\0');
|
|
status =
|
|
WithOecReadLock("GetTokenFromEmbeddedCertificate - attempt 2", [&] {
|
|
return OEMCrypto_GetEmbeddedDrmCertificate(
|
|
reinterpret_cast<uint8_t*>(&embedded_certificate.front()),
|
|
&certificate_length);
|
|
});
|
|
}
|
|
|
|
if (status == OEMCrypto_SUCCESS) {
|
|
// TODO: b/312782308 - Store embedded certificate as SignedDrmCertificate
|
|
video_widevine_client::sdk::HashedFile hashed_file;
|
|
video_widevine_client::sdk::File file;
|
|
embedded_certificate.resize(certificate_length);
|
|
if (hashed_file.ParseFromString(embedded_certificate) &&
|
|
file.ParseFromString(hashed_file.file())) {
|
|
*token = file.device_certificate().certificate();
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
}
|
|
|
|
token->clear();
|
|
return MapOEMCryptoResult(status, GET_TOKEN_FROM_EMBEDDED_CERT_ERROR,
|
|
"GetTokenFromEmbeddedCertificate");
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetDeviceInformation(
|
|
RequestedSecurityLevel requested_security_level, std::string* device_info) {
|
|
RETURN_IF_NULL(device_info, PARAMETER_NULL);
|
|
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
|
|
if (GetSecurityLevel(requested_security_level) != kSecurityLevelL1) {
|
|
LOGE("CDM only supports L1 device_info");
|
|
return CdmResponseType(NOT_IMPLEMENTED_ERROR);
|
|
}
|
|
CdmClientTokenType token_type = kClientTokenUninitialized;
|
|
const CdmResponseType status =
|
|
GetProvisioningMethod(requested_security_level, &token_type);
|
|
if (status != NO_ERROR) {
|
|
LOGE("Failed to get token type");
|
|
return status;
|
|
}
|
|
if (token_type != kClientTokenBootCertChain) {
|
|
return CdmResponseType(
|
|
PROVISIONING_TYPE_IS_NOT_BOOT_CERTIFICATE_CHAIN_ERROR);
|
|
}
|
|
|
|
size_t device_info_length = 0;
|
|
OEMCryptoResult sts = WithOecReadLock("GetDeviceInformation Attempt 1", [&] {
|
|
return OEMCrypto_GetDeviceInformation(nullptr, &device_info_length);
|
|
});
|
|
if (sts == OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
device_info->resize(device_info_length);
|
|
sts = WithOecReadLock("GetDeviceInformation Attempt 2", [&] {
|
|
return OEMCrypto_GetDeviceInformation(
|
|
MutableStringDataPointer(device_info), &device_info_length);
|
|
});
|
|
}
|
|
if (sts != OEMCrypto_SUCCESS) {
|
|
LOGE("OEMCrypto_GetDeviceInformation failed: status = %d",
|
|
static_cast<int>(sts));
|
|
device_info->clear();
|
|
return MapOEMCryptoResult(sts, GET_DEVICE_INFORMATION_ERROR,
|
|
"GetDeviceInformation");
|
|
}
|
|
device_info->resize(device_info_length);
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetDeviceSignedCsrPayload(
|
|
RequestedSecurityLevel requested_security_level,
|
|
const std::string& challenge, const std::string& device_info,
|
|
std::string* signed_csr_payload) {
|
|
RETURN_IF_NULL(signed_csr_payload, PARAMETER_NULL);
|
|
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
|
|
if (GetSecurityLevel(requested_security_level) != kSecurityLevelL1) {
|
|
LOGE("CDM only supports L1 CSR payload");
|
|
return CdmResponseType(NOT_IMPLEMENTED_ERROR);
|
|
}
|
|
CdmClientTokenType token_type = kClientTokenUninitialized;
|
|
const CdmResponseType status =
|
|
GetProvisioningMethod(requested_security_level, &token_type);
|
|
if (status != NO_ERROR) {
|
|
LOGE("Failed to get token type");
|
|
return status;
|
|
}
|
|
if (token_type != kClientTokenBootCertChain) {
|
|
return CdmResponseType(
|
|
PROVISIONING_TYPE_IS_NOT_BOOT_CERTIFICATE_CHAIN_ERROR);
|
|
}
|
|
|
|
size_t signed_csr_payload_length = 0;
|
|
OEMCryptoResult sts =
|
|
WithOecReadLock("GetDeviceSignedCsrPayload Attempt 1", [&] {
|
|
return OEMCrypto_GetDeviceSignedCsrPayload(
|
|
reinterpret_cast<const uint8_t*>(challenge.data()),
|
|
challenge.size(),
|
|
reinterpret_cast<const uint8_t*>(device_info.data()),
|
|
device_info.size(), nullptr, &signed_csr_payload_length);
|
|
});
|
|
if (sts == OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
signed_csr_payload->resize(signed_csr_payload_length);
|
|
sts = WithOecReadLock("GetDeviceSignedCsrPayload Attempt 2", [&] {
|
|
return OEMCrypto_GetDeviceSignedCsrPayload(
|
|
reinterpret_cast<const uint8_t*>(challenge.data()), challenge.size(),
|
|
reinterpret_cast<const uint8_t*>(device_info.data()),
|
|
device_info.size(), MutableStringDataPointer(signed_csr_payload),
|
|
&signed_csr_payload_length);
|
|
});
|
|
}
|
|
if (sts != OEMCrypto_SUCCESS) {
|
|
LOGE("OEMCrypto_GetDeviceSignedCsrPayload failed: status = %d",
|
|
static_cast<int>(sts));
|
|
signed_csr_payload->clear();
|
|
return MapOEMCryptoResult(sts, GET_DEVICE_SIGNED_CSR_PAYLOAD_ERROR,
|
|
"GetDeviceSignedCsrPayload");
|
|
}
|
|
signed_csr_payload->resize(signed_csr_payload_length);
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GenerateCertificateKeyPair(
|
|
std::string* public_key, std::string* public_key_signature,
|
|
std::string* wrapped_private_key, CryptoWrappedKey::Type* key_type) {
|
|
LOGV("Generating certificate key pair: id = %u", oec_session_id_);
|
|
RETURN_IF_NULL(public_key, PARAMETER_NULL);
|
|
RETURN_IF_NULL(public_key_signature, PARAMETER_NULL);
|
|
RETURN_IF_NULL(wrapped_private_key, PARAMETER_NULL);
|
|
RETURN_IF_NULL(key_type, PARAMETER_NULL);
|
|
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
|
|
|
|
// Round 1, get the size of all the fields.
|
|
size_t public_key_length = 0;
|
|
size_t public_key_signature_length = 0;
|
|
size_t wrapped_private_key_length = 0;
|
|
OEMCrypto_PrivateKeyType oemcrypto_key_type;
|
|
OEMCryptoResult status;
|
|
WithOecSessionLock("GenerateCertificateKeyPair Attempt 1", [&] {
|
|
M_TIME(status = OEMCrypto_GenerateCertificateKeyPair(
|
|
oec_session_id_, nullptr, &public_key_length, nullptr,
|
|
&public_key_signature_length, nullptr,
|
|
&wrapped_private_key_length, &oemcrypto_key_type),
|
|
metrics_, oemcrypto_generate_certificate_key_pair_, status);
|
|
});
|
|
|
|
if (status != OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
return MapOEMCryptoResult(status, GENERATE_CERTIFICATE_KEY_PAIR_ERROR,
|
|
"GenerateCertificateKeyPair");
|
|
}
|
|
|
|
public_key->resize(public_key_length);
|
|
public_key_signature->resize(public_key_signature_length);
|
|
wrapped_private_key->resize(wrapped_private_key_length);
|
|
WithOecSessionLock("GenerateCertificateKeyPair Attempt 2", [&] {
|
|
M_TIME(
|
|
status = OEMCrypto_GenerateCertificateKeyPair(
|
|
oec_session_id_, MutableStringDataPointer(public_key),
|
|
&public_key_length, MutableStringDataPointer(public_key_signature),
|
|
&public_key_signature_length,
|
|
MutableStringDataPointer(wrapped_private_key),
|
|
&wrapped_private_key_length, &oemcrypto_key_type),
|
|
metrics_, oemcrypto_generate_certificate_key_pair_, status);
|
|
});
|
|
if (status != OEMCrypto_SUCCESS) {
|
|
return MapOEMCryptoResult(status, GENERATE_CERTIFICATE_KEY_PAIR_ERROR,
|
|
"GenerateCertificateKeyPair");
|
|
}
|
|
|
|
public_key->resize(public_key_length);
|
|
public_key_signature->resize(public_key_signature_length);
|
|
wrapped_private_key->resize(wrapped_private_key_length);
|
|
|
|
if (oemcrypto_key_type == OEMCrypto_RSA_Private_Key) {
|
|
*key_type = CryptoWrappedKey::kRsa;
|
|
} else if (oemcrypto_key_type == OEMCrypto_ECC_Private_Key) {
|
|
*key_type = CryptoWrappedKey::kEcc;
|
|
} else {
|
|
LOGE("Unexpected key type returned from GenerateCertificateKeyPair: %d",
|
|
static_cast<int>(oemcrypto_key_type));
|
|
// TODO(b/261185349): add OEMCrypto_PrivateKeyType to CdmResponseType
|
|
return CdmResponseType(GENERATE_CERTIFICATE_KEY_PAIR_UNKNOWN_TYPE_ERROR);
|
|
}
|
|
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
|
|
CdmResponseType CryptoSession::LoadOemCertificatePrivateKey(
|
|
const CryptoWrappedKey& private_key) {
|
|
LOGV("Load OEM cert and private key: id = %u", oec_session_id_);
|
|
const OEMCrypto_PrivateKeyType key_type =
|
|
(private_key.type() == CryptoWrappedKey::kEcc)
|
|
? OEMCrypto_ECC_Private_Key
|
|
: OEMCrypto_RSA_Private_Key;
|
|
const std::string& wrapped_private_key = private_key.key();
|
|
|
|
OEMCryptoResult status;
|
|
WithOecSessionLock("InstallOemPrivateKey", [&] {
|
|
M_TIME(status = OEMCrypto_InstallOemPrivateKey(
|
|
oec_session_id_, key_type,
|
|
reinterpret_cast<const uint8_t*>(wrapped_private_key.data()),
|
|
wrapped_private_key.size()),
|
|
metrics_, oemcrypto_install_oem_private_key_, status);
|
|
});
|
|
|
|
return MapOEMCryptoResult(status, LOAD_OEM_CERTIFICATE_PRIVATE_KEY_ERROR,
|
|
"InstallOemPrivateKey");
|
|
}
|
|
|
|
// Private.
|
|
CdmResponseType CryptoSession::SelectKey(const std::string& key_id,
|
|
CdmCipherMode cipher_mode) {
|
|
RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN);
|
|
const OEMCryptoResult sts = WithOecSessionLock("SelectKey", [&] {
|
|
RETURN_IF_NULL(key_session_, OEMCrypto_ERROR_INVALID_SESSION);
|
|
return key_session_->SelectKey(key_id, cipher_mode);
|
|
});
|
|
|
|
if (sts != last_select_key_error_) {
|
|
// Only log errors on first occurrence. Calls to SelectKey from
|
|
// the app are typically queued. If an error occurs, then it is
|
|
// expected that multiple failures will occur before the app is
|
|
// notified and stops queueing calls to the CDM.
|
|
last_select_key_error_ = sts;
|
|
if (sts == OEMCrypto_ERROR_INSUFFICIENT_HDCP ||
|
|
sts == OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION) {
|
|
const char* status_message = OemCryptoResultToString(sts);
|
|
HdcpCapability current_hdcp_level;
|
|
HdcpCapability max_hdcp_level;
|
|
const CdmResponseType hdcp_status =
|
|
GetHdcpCapabilities(¤t_hdcp_level, &max_hdcp_level);
|
|
const char* current_hdcp_message = kUnavailableHdcpLevel;
|
|
const char* max_hdcp_message = kUnavailableHdcpLevel;
|
|
if (hdcp_status == NO_ERROR) {
|
|
current_hdcp_message = HdcpCapabilityToString(current_hdcp_level);
|
|
max_hdcp_message = HdcpCapabilityToString(max_hdcp_level);
|
|
}
|
|
LOGE(
|
|
"oec_session_id = %u, security_level = %s, "
|
|
"status = %s, current_hdcp_level = %s, "
|
|
"max_hdcp_level = %s",
|
|
oec_session_id_,
|
|
RequestedSecurityLevelToString(requested_security_level_),
|
|
status_message, current_hdcp_message, max_hdcp_message);
|
|
}
|
|
}
|
|
|
|
switch (sts) {
|
|
// SelectKey errors.
|
|
case OEMCrypto_SUCCESS:
|
|
return CdmResponseType(NO_ERROR);
|
|
case OEMCrypto_ERROR_KEY_EXPIRED:
|
|
return CRYPTO_ERROR(NEED_KEY, sts);
|
|
case OEMCrypto_ERROR_INVALID_SESSION:
|
|
return CRYPTO_ERROR(INVALID_SESSION_1, sts);
|
|
case OEMCrypto_ERROR_NO_DEVICE_KEY:
|
|
return CRYPTO_ERROR(NO_DEVICE_KEY_1, sts);
|
|
case OEMCrypto_ERROR_NO_CONTENT_KEY:
|
|
return CRYPTO_ERROR(NO_CONTENT_KEY_2, sts);
|
|
case OEMCrypto_ERROR_CONTROL_INVALID:
|
|
case OEMCrypto_ERROR_KEYBOX_INVALID:
|
|
return CRYPTO_ERROR(UNKNOWN_SELECT_KEY_ERROR_2, sts);
|
|
case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES:
|
|
return CRYPTO_ERROR(INSUFFICIENT_CRYPTO_RESOURCES, sts);
|
|
case OEMCrypto_ERROR_UNKNOWN_FAILURE:
|
|
return CRYPTO_ERROR(UNKNOWN_SELECT_KEY_ERROR_1, sts);
|
|
case OEMCrypto_ERROR_ANALOG_OUTPUT:
|
|
return CRYPTO_ERROR(ANALOG_OUTPUT_ERROR, sts);
|
|
case OEMCrypto_ERROR_INSUFFICIENT_HDCP:
|
|
return CRYPTO_ERROR(INSUFFICIENT_OUTPUT_PROTECTION, sts);
|
|
// LoadEntitledContentKeys errors.
|
|
// |key_session_| may make calls to OEMCrypto_LoadEntitledContentKeys
|
|
// if the key selected has not yet been loaded.
|
|
case OEMCrypto_ERROR_INVALID_CONTEXT:
|
|
return CRYPTO_ERROR(NOT_AN_ENTITLEMENT_SESSION, sts);
|
|
case OEMCrypto_KEY_NOT_ENTITLED:
|
|
return CRYPTO_ERROR(NO_MATCHING_ENTITLEMENT_KEY, sts);
|
|
// Obsolete errors.
|
|
case OEMCrypto_KEY_NOT_LOADED:
|
|
return CRYPTO_ERROR(NO_CONTENT_KEY_3, sts);
|
|
// Catch all else.
|
|
default:
|
|
return MapOEMCryptoResult(sts, UNKNOWN_SELECT_KEY_ERROR_2, "SelectKey");
|
|
}
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GenerateRsaSignature(const std::string& message,
|
|
std::string* signature,
|
|
RSA_Padding_Scheme scheme) {
|
|
LOGV("Generating RSA signature: id = %u", oec_session_id_);
|
|
RETURN_IF_NULL(signature, PARAMETER_NULL);
|
|
|
|
OEMCryptoResult sts;
|
|
signature->resize(kRsaSignatureLength);
|
|
size_t length = signature->size();
|
|
|
|
// At most two attempts.
|
|
// The first attempt may fail due to buffer too short
|
|
for (int i = 0; i < 2; ++i) {
|
|
WithOecSessionLock("GenerateRsaSignature", [&] {
|
|
M_TIME(
|
|
sts = OEMCrypto_GenerateRSASignature(
|
|
oec_session_id_, reinterpret_cast<const uint8_t*>(message.data()),
|
|
message.size(),
|
|
reinterpret_cast<uint8_t*>(const_cast<char*>(signature->data())),
|
|
&length, scheme),
|
|
metrics_, oemcrypto_generate_rsa_signature_, sts,
|
|
metrics::Pow2Bucket(length));
|
|
});
|
|
|
|
if (OEMCrypto_SUCCESS == sts) {
|
|
// Trim signature buffer and done
|
|
signature->resize(length);
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
if (OEMCrypto_ERROR_SHORT_BUFFER != sts) {
|
|
break;
|
|
}
|
|
|
|
// Retry with proper-sized signature buffer
|
|
signature->resize(length);
|
|
}
|
|
|
|
return MapOEMCryptoResult(sts, RSA_SIGNATURE_GENERATION_ERROR,
|
|
"OEMCrypto_GenerateRSASignature");
|
|
}
|
|
|
|
size_t CryptoSession::GetMaxSubsampleRegionSize() {
|
|
// If we haven't cached the answer yet, fetch it from OEMCrypto.
|
|
if (max_subsample_region_size_ == 0) {
|
|
uint32_t tier = 0;
|
|
if (GetResourceRatingTier(&tier)) {
|
|
// Subtract RESOURCE_RATING_TIER_MIN to get a 0-based index into the
|
|
// table.
|
|
const uint32_t index = tier - RESOURCE_RATING_TIER_MIN;
|
|
if (index < wvutil::ArraySize(kMaxSubsampleRegionSizes)) {
|
|
max_subsample_region_size_ = kMaxSubsampleRegionSizes[index];
|
|
}
|
|
}
|
|
|
|
// If something went wrong, use the default.
|
|
if (max_subsample_region_size_ == 0) {
|
|
LOGW("Unable to get maximum subsample region size. Defaulting to %zu",
|
|
kDefaultMaxSubsampleRegionSize);
|
|
max_subsample_region_size_ = kDefaultMaxSubsampleRegionSize;
|
|
}
|
|
}
|
|
|
|
return max_subsample_region_size_;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::Decrypt(
|
|
const CdmDecryptionParametersV16& params) {
|
|
if (!is_destination_buffer_type_valid_) {
|
|
if (!SetDestinationBufferType()) return CdmResponseType(UNKNOWN_ERROR);
|
|
}
|
|
|
|
OEMCryptoBufferType output_descriptor_type =
|
|
params.is_secure ? destination_buffer_type_ : OEMCrypto_BufferType_Clear;
|
|
if (params.is_secure &&
|
|
output_descriptor_type == OEMCrypto_BufferType_Clear) {
|
|
return CdmResponseType(SECURE_BUFFER_REQUIRED);
|
|
}
|
|
|
|
if (params.samples.size() == 0)
|
|
return CdmResponseType(CANNOT_DECRYPT_ZERO_SAMPLES);
|
|
if (std::any_of(std::begin(params.samples), std::end(params.samples),
|
|
[](const CdmDecryptionSample& sample) -> bool {
|
|
return sample.subsamples.size() == 0;
|
|
})) {
|
|
return CdmResponseType(CANNOT_DECRYPT_ZERO_SUBSAMPLES);
|
|
}
|
|
|
|
// Convert all the sample and subsample definitions to OEMCrypto structs.
|
|
// This code also caches whether any of the data is protected, to save later
|
|
// code the trouble of iterating over all the subsamples to check.
|
|
bool is_any_sample_protected = false;
|
|
std::vector<OEMCrypto_SampleDescription> oec_samples;
|
|
oec_samples.reserve(params.samples.size());
|
|
std::vector<std::vector<OEMCrypto_SubSampleDescription>>
|
|
oec_subsample_vectors;
|
|
oec_subsample_vectors.reserve(params.samples.size());
|
|
|
|
for (const CdmDecryptionSample& sample : params.samples) {
|
|
oec_samples.emplace_back();
|
|
OEMCrypto_SampleDescription& oec_sample = oec_samples.back();
|
|
|
|
// Set up the sample's input buffer
|
|
oec_sample.buffers.input_data = sample.encrypt_buffer;
|
|
oec_sample.buffers.input_data_length = sample.encrypt_buffer_length;
|
|
|
|
// Set up the sample's output buffer
|
|
OEMCrypto_DestBufferDesc& output_descriptor =
|
|
oec_sample.buffers.output_descriptor;
|
|
output_descriptor.type = output_descriptor_type;
|
|
switch (output_descriptor.type) {
|
|
case OEMCrypto_BufferType_Clear:
|
|
output_descriptor.buffer.clear.clear_buffer =
|
|
static_cast<uint8_t*>(sample.decrypt_buffer) +
|
|
sample.decrypt_buffer_offset;
|
|
output_descriptor.buffer.clear.clear_buffer_length =
|
|
sample.decrypt_buffer_size - sample.decrypt_buffer_offset;
|
|
break;
|
|
case OEMCrypto_BufferType_Secure:
|
|
output_descriptor.buffer.secure.secure_buffer = sample.decrypt_buffer;
|
|
output_descriptor.buffer.secure.offset = sample.decrypt_buffer_offset;
|
|
output_descriptor.buffer.secure.secure_buffer_length =
|
|
sample.decrypt_buffer_size;
|
|
break;
|
|
case OEMCrypto_BufferType_Direct:
|
|
output_descriptor.buffer.direct.is_video = params.is_video;
|
|
break;
|
|
}
|
|
|
|
// Convert all the sample's subsamples. This code also tallies the total
|
|
// size as a sanity check.
|
|
oec_subsample_vectors.emplace_back();
|
|
std::vector<OEMCrypto_SubSampleDescription>& oec_subsamples =
|
|
oec_subsample_vectors.back();
|
|
oec_subsamples.reserve(sample.subsamples.size());
|
|
size_t sample_size = 0;
|
|
bool is_any_subsample_protected = false;
|
|
size_t current_block_offset = 0;
|
|
for (const CdmDecryptionSubsample& subsample : sample.subsamples) {
|
|
oec_subsamples.push_back(OEMCrypto_SubSampleDescription{
|
|
subsample.clear_bytes, subsample.protected_bytes,
|
|
0, // subsample_flags
|
|
current_block_offset});
|
|
|
|
is_any_subsample_protected |= (subsample.protected_bytes > 0);
|
|
sample_size += subsample.clear_bytes + subsample.protected_bytes;
|
|
if (params.cipher_mode == kCipherModeCtr) {
|
|
current_block_offset =
|
|
(current_block_offset + subsample.protected_bytes) %
|
|
kAes128BlockSize;
|
|
}
|
|
|
|
// TODO(b/149524614): This block is not necessary except for
|
|
// backwards-compatibility while we are transitioning from the v15 API to
|
|
// the v16 API.
|
|
if (params.observe_legacy_fields) {
|
|
OEMCrypto_SubSampleDescription& oec_subsample = oec_subsamples.back();
|
|
oec_subsample.subsample_flags = subsample.flags;
|
|
oec_subsample.block_offset = subsample.block_offset;
|
|
}
|
|
}
|
|
|
|
is_any_sample_protected |= is_any_subsample_protected;
|
|
|
|
// TODO(b/149524614): This check is not necessary except for
|
|
// backwards-compatibility while we are transitioning from the v15 API to
|
|
// the v16 API.
|
|
if (!params.observe_legacy_fields) {
|
|
// Set the actual subsample_flags now that all the subsamples are
|
|
// converted.
|
|
oec_subsamples.front().subsample_flags |= OEMCrypto_FirstSubsample;
|
|
oec_subsamples.back().subsample_flags |= OEMCrypto_LastSubsample;
|
|
}
|
|
|
|
// Check that the total size is valid
|
|
if (sample_size != oec_sample.buffers.input_data_length)
|
|
return CdmResponseType(SAMPLE_AND_SUBSAMPLE_SIZE_MISMATCH);
|
|
|
|
// Set up the sample's IV
|
|
if (is_any_subsample_protected) {
|
|
if (sizeof(oec_sample.iv) != sample.iv.size())
|
|
return CdmResponseType(INVALID_IV_SIZE);
|
|
memcpy(oec_sample.iv, sample.iv.data(), sizeof(oec_sample.iv));
|
|
} else {
|
|
memset(oec_sample.iv, 0, sizeof(oec_sample.iv));
|
|
}
|
|
|
|
// Attach the subsamples to the sample description
|
|
oec_sample.subsamples = oec_subsamples.data();
|
|
oec_sample.subsamples_length = oec_subsamples.size();
|
|
}
|
|
|
|
// Convert the pattern descriptor
|
|
OEMCrypto_CENCEncryptPatternDesc oec_pattern{params.pattern.encrypt_blocks,
|
|
params.pattern.skip_blocks};
|
|
// TODO(b/146581957): Remove this workaround once OEMCrypto treats (0,0) as
|
|
// 'cbcs' instead of 'cbc1'.
|
|
if (params.cipher_mode == kCipherModeCbc && oec_pattern.encrypt == 0 &&
|
|
oec_pattern.skip == 0) {
|
|
// (10, 0) is the preferred pattern for decrypting every block in 'cbcs'
|
|
oec_pattern.encrypt = 10;
|
|
}
|
|
|
|
// Check if a key needs to be selected
|
|
if (is_any_sample_protected) {
|
|
CdmResponseType result = SelectKey(params.key_id, params.cipher_mode);
|
|
if (result != NO_ERROR) return result;
|
|
}
|
|
|
|
// Perform decrypt
|
|
const OEMCryptoResult sts = DecryptMultipleSamples(
|
|
oec_samples, params.cipher_mode, oec_pattern, is_any_sample_protected);
|
|
|
|
if (sts != OEMCrypto_SUCCESS && last_decrypt_error_ != sts) {
|
|
// Decrypt errors and warnings are only logged when the error code
|
|
// changes. This is in anticipation that if an error code is
|
|
// returned, then the same error code is likely to be returned in
|
|
// the next call. The calling application may make several more
|
|
// decrypt requests before the error is handled by the app.
|
|
last_decrypt_error_ = sts;
|
|
if (sts == OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION ||
|
|
sts == OEMCrypto_ERROR_INSUFFICIENT_HDCP) {
|
|
const char* status_message = OemCryptoResultToString(sts);
|
|
HdcpCapability current_hdcp_level;
|
|
HdcpCapability max_hdcp_level;
|
|
const CdmResponseType hdcp_status =
|
|
GetHdcpCapabilities(¤t_hdcp_level, &max_hdcp_level);
|
|
const char* current_hdcp_message = kUnavailableHdcpLevel;
|
|
const char* max_hdcp_message = kUnavailableHdcpLevel;
|
|
if (hdcp_status == NO_ERROR) {
|
|
current_hdcp_message = HdcpCapabilityToString(current_hdcp_level);
|
|
max_hdcp_message = HdcpCapabilityToString(max_hdcp_level);
|
|
}
|
|
LOGE(
|
|
"OEMCrypto_DecryptCENC failed: oec_session_id = %u, "
|
|
"security_level = %s, status = %s, current_hdcp_level = %s, "
|
|
"max_hdcp_level = %s",
|
|
oec_session_id_,
|
|
RequestedSecurityLevelToString(requested_security_level_),
|
|
status_message, current_hdcp_message, max_hdcp_message);
|
|
} else {
|
|
LOGE(
|
|
"OEMCrypto_DecryptCENC failed: oec_session_id = %u, "
|
|
"security_level = %s, status = %d",
|
|
oec_session_id_,
|
|
RequestedSecurityLevelToString(requested_security_level_),
|
|
static_cast<int>(sts));
|
|
}
|
|
}
|
|
|
|
switch (sts) {
|
|
case OEMCrypto_SUCCESS:
|
|
case OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION:
|
|
return CdmResponseType(NO_ERROR);
|
|
case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES:
|
|
return CRYPTO_ERROR(INSUFFICIENT_CRYPTO_RESOURCES, sts);
|
|
case OEMCrypto_ERROR_KEY_EXPIRED:
|
|
return CRYPTO_ERROR(NEED_KEY, sts);
|
|
case OEMCrypto_ERROR_INVALID_SESSION:
|
|
return CRYPTO_ERROR(INVALID_SESSION_2, sts);
|
|
case OEMCrypto_ERROR_DECRYPT_FAILED:
|
|
case OEMCrypto_ERROR_UNKNOWN_FAILURE:
|
|
return CRYPTO_ERROR(DECRYPT_ERROR, sts);
|
|
case OEMCrypto_ERROR_INSUFFICIENT_HDCP:
|
|
return CRYPTO_ERROR(INSUFFICIENT_OUTPUT_PROTECTION, sts);
|
|
case OEMCrypto_ERROR_ANALOG_OUTPUT:
|
|
return CRYPTO_ERROR(ANALOG_OUTPUT_ERROR, sts);
|
|
case OEMCrypto_ERROR_OUTPUT_TOO_LARGE:
|
|
return CRYPTO_ERROR(OUTPUT_TOO_LARGE_ERROR, sts);
|
|
case OEMCrypto_ERROR_SESSION_LOST_STATE:
|
|
return CRYPTO_ERROR(SESSION_LOST_STATE_ERROR, sts);
|
|
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
|
|
return CRYPTO_ERROR(SYSTEM_INVALIDATED_ERROR, sts);
|
|
default:
|
|
return CRYPTO_ERROR(UNKNOWN_ERROR, sts);
|
|
}
|
|
}
|
|
|
|
bool CryptoSession::HasUsageTableSupport(bool* has_support) {
|
|
RETURN_IF_NOT_OPEN(false);
|
|
RETURN_IF_NULL(has_support, false);
|
|
return WithOecReadLock("HasUsageTableSupport", [&] {
|
|
// Use cached value if set.
|
|
if (has_usage_table_support_ != kBooleanUnset) {
|
|
*has_support = (has_usage_table_support_ == kBooleanTrue);
|
|
return true;
|
|
}
|
|
if (!HasUsageTableSupportInternal(requested_security_level_, has_support)) {
|
|
return false;
|
|
}
|
|
// Cache result if successful.
|
|
has_usage_table_support_ = (*has_support ? kBooleanTrue : kBooleanFalse);
|
|
return true;
|
|
});
|
|
}
|
|
|
|
bool CryptoSession::HasUsageTableSupport(
|
|
RequestedSecurityLevel requested_security_level, bool* has_support) {
|
|
RETURN_IF_UNINITIALIZED(false);
|
|
RETURN_IF_NULL(has_support, false);
|
|
return WithOecReadLock("HasUsageTableSupport", [&] {
|
|
return HasUsageTableSupportInternal(requested_security_level, has_support);
|
|
});
|
|
}
|
|
|
|
bool CryptoSession::HasUsageTableSupportInternal(
|
|
RequestedSecurityLevel requested_security_level, bool* has_support) {
|
|
LOGV("requested_security_level = %s",
|
|
RequestedSecurityLevelToString(requested_security_level));
|
|
*has_support = WithOecReadLock("HasUsageTableSupport", [&] {
|
|
return OEMCrypto_SupportsUsageTable(requested_security_level);
|
|
});
|
|
return true;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::DeactivateUsageInformation(
|
|
const std::string& provider_session_token) {
|
|
LOGV("Deactivating usage information: id = %u", oec_session_id_);
|
|
|
|
uint8_t* pst = reinterpret_cast<uint8_t*>(
|
|
const_cast<char*>(provider_session_token.data()));
|
|
|
|
// TODO(fredgc or rfrias): make sure oec_session_id_ is valid.
|
|
OEMCryptoResult status;
|
|
WithOecWriteLock("DeactivateUsageInformation", [&] {
|
|
status = OEMCrypto_DeactivateUsageEntry(oec_session_id_, pst,
|
|
provider_session_token.length());
|
|
});
|
|
metrics_->oemcrypto_deactivate_usage_entry_.Increment(status);
|
|
|
|
if (status != OEMCrypto_SUCCESS) {
|
|
LOGE("OEMCrypto_DeactivateUsageEntry failed: status = %d",
|
|
static_cast<int>(status));
|
|
}
|
|
|
|
switch (status) {
|
|
case OEMCrypto_SUCCESS:
|
|
return CdmResponseType(NO_ERROR);
|
|
case OEMCrypto_ERROR_INVALID_CONTEXT:
|
|
return CRYPTO_ERROR(KEY_CANCELED, status);
|
|
case OEMCrypto_ERROR_SESSION_LOST_STATE:
|
|
return CRYPTO_ERROR(SESSION_LOST_STATE_ERROR, status);
|
|
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
|
|
return CRYPTO_ERROR(SYSTEM_INVALIDATED_ERROR, status);
|
|
default:
|
|
return CRYPTO_ERROR(DEACTIVATE_USAGE_ENTRY_ERROR, status);
|
|
}
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GenerateUsageReport(
|
|
const std::string& provider_session_token, std::string* usage_report,
|
|
UsageDurationStatus* usage_duration_status, int64_t* seconds_since_started,
|
|
int64_t* seconds_since_last_played) {
|
|
LOGV("Generating usage report: id = %u", oec_session_id_);
|
|
|
|
RETURN_IF_NULL(usage_report, PARAMETER_NULL);
|
|
|
|
uint8_t* pst = reinterpret_cast<uint8_t*>(
|
|
const_cast<char*>(provider_session_token.data()));
|
|
|
|
size_t usage_length = 0;
|
|
OEMCryptoResult status;
|
|
WithOecWriteLock("GenerateUsageReport Attempt 1", [&] {
|
|
status = OEMCrypto_ReportUsage(oec_session_id_, pst,
|
|
provider_session_token.length(), nullptr,
|
|
&usage_length);
|
|
});
|
|
metrics_->oemcrypto_report_usage_.Increment(status);
|
|
|
|
if (status != OEMCrypto_SUCCESS && status != OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
return MapOEMCryptoResult(status, GENERATE_USAGE_REPORT_ERROR,
|
|
"GenerateUsageReport");
|
|
}
|
|
|
|
std::vector<uint8_t> buffer(usage_length);
|
|
|
|
WithOecWriteLock("GenerateUsageReport Attempt 2", [&] {
|
|
status = OEMCrypto_ReportUsage(oec_session_id_, pst,
|
|
provider_session_token.length(), &buffer[0],
|
|
&usage_length);
|
|
});
|
|
metrics_->oemcrypto_report_usage_.Increment(status);
|
|
|
|
if (status != OEMCrypto_SUCCESS) {
|
|
return MapOEMCryptoResult(status, GENERATE_USAGE_REPORT_ERROR,
|
|
"OEMCrypto_ReportUsage");
|
|
}
|
|
|
|
if (usage_length != buffer.size()) {
|
|
buffer.resize(usage_length);
|
|
}
|
|
(*usage_report) =
|
|
std::string(reinterpret_cast<const char*>(&buffer[0]), buffer.size());
|
|
|
|
wvutil::Unpacked_PST_Report pst_report(&buffer[0]);
|
|
*usage_duration_status = kUsageDurationsInvalid;
|
|
if (usage_length < pst_report.report_size()) {
|
|
LOGE(
|
|
"Parsed usage report smaller than expected: "
|
|
"usage_length = %zu, report_size = %zu",
|
|
usage_length, pst_report.report_size());
|
|
return CdmResponseType(
|
|
NO_ERROR); // usage report available but no duration information
|
|
}
|
|
|
|
if (kUnused == pst_report.status()) {
|
|
*usage_duration_status = kUsageDurationPlaybackNotBegun;
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
LOGV("OEMCrypto_PST_Report.status: %d\n",
|
|
static_cast<int>(pst_report.status()));
|
|
LOGV("OEMCrypto_PST_Report.clock_security_level: %d\n",
|
|
static_cast<int>(pst_report.clock_security_level()));
|
|
LOGV("OEMCrypto_PST_Report.pst_length: %d\n",
|
|
static_cast<int>(pst_report.pst_length()));
|
|
LOGV("OEMCrypto_PST_Report.padding: %d\n",
|
|
static_cast<int>(pst_report.padding()));
|
|
LOGV("OEMCrypto_PST_Report.seconds_since_license_received: %" PRId64 "\n",
|
|
pst_report.seconds_since_license_received());
|
|
LOGV("OEMCrypto_PST_Report.seconds_since_first_decrypt: %" PRId64 "\n",
|
|
pst_report.seconds_since_first_decrypt());
|
|
LOGV("OEMCrypto_PST_Report.seconds_since_last_decrypt: %" PRId64 "\n",
|
|
pst_report.seconds_since_last_decrypt());
|
|
LOGV("OEMCrypto_PST_Report: %s\n", wvutil::b2a_hex(*usage_report).c_str());
|
|
|
|
if (kInactiveUnused == pst_report.status()) {
|
|
*usage_duration_status = kUsageDurationPlaybackNotBegun;
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
// Before OEMCrypto v13, When usage report state is inactive, we have to
|
|
// deduce whether the license was ever used.
|
|
if (kInactive == pst_report.status() &&
|
|
(0 > pst_report.seconds_since_first_decrypt() ||
|
|
pst_report.seconds_since_license_received() <
|
|
pst_report.seconds_since_first_decrypt())) {
|
|
*usage_duration_status = kUsageDurationPlaybackNotBegun;
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
|
|
*usage_duration_status = kUsageDurationsValid;
|
|
*seconds_since_started = pst_report.seconds_since_first_decrypt();
|
|
*seconds_since_last_played = pst_report.seconds_since_last_decrypt();
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
|
|
bool CryptoSession::IsAntiRollbackHwPresent() {
|
|
bool is_present;
|
|
WithOecReadLock("IsAntiRollbackHwPresent", [&] {
|
|
is_present = OEMCrypto_IsAntiRollbackHwPresent(requested_security_level_);
|
|
});
|
|
metrics_->oemcrypto_is_anti_rollback_hw_present_.Record(is_present);
|
|
return is_present;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GenerateNonce(uint32_t* nonce) {
|
|
RETURN_IF_NULL(nonce, PARAMETER_NULL);
|
|
|
|
OEMCryptoResult result;
|
|
WithOecWriteLock("GenerateNonce", [&] {
|
|
result = OEMCrypto_GenerateNonce(oec_session_id_, nonce);
|
|
});
|
|
metrics_->oemcrypto_generate_nonce_.Increment(result);
|
|
|
|
return MapOEMCryptoResult(result, NONCE_GENERATION_ERROR, "GenerateNonce");
|
|
}
|
|
|
|
bool CryptoSession::SetDestinationBufferType() {
|
|
if (Properties::oem_crypto_use_secure_buffers()) {
|
|
destination_buffer_type_ = OEMCrypto_BufferType_Secure;
|
|
} else if (Properties::oem_crypto_use_fifo()) {
|
|
destination_buffer_type_ = OEMCrypto_BufferType_Direct;
|
|
} else if (Properties::oem_crypto_use_userspace_buffers()) {
|
|
destination_buffer_type_ = OEMCrypto_BufferType_Clear;
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
is_destination_buffer_type_valid_ = true;
|
|
return true;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::LoadProvisioning(
|
|
const std::string& request, const std::string& signed_message,
|
|
const std::string& core_message, const std::string& signature,
|
|
std::string* wrapped_private_key) {
|
|
LOGV("Loading provisioning certificate: id = %u", oec_session_id_);
|
|
if (wrapped_private_key == nullptr) {
|
|
LOGE("Missing wrapped |wrapped_private_key|");
|
|
return CdmResponseType(PARAMETER_NULL);
|
|
}
|
|
|
|
const std::string combined_message = core_message + signed_message;
|
|
// Round 1, get the size of the wrapped private key buffer.
|
|
size_t wrapped_private_key_length = 0;
|
|
OEMCryptoResult status;
|
|
WithOecSessionLock("LoadProvisioning Attempt 1", [&] {
|
|
M_TIME(status = OEMCrypto_LoadProvisioning(
|
|
oec_session_id_,
|
|
reinterpret_cast<const uint8_t*>(request.data()), request.size(),
|
|
reinterpret_cast<const uint8_t*>(combined_message.data()),
|
|
combined_message.size(), core_message.size(),
|
|
reinterpret_cast<const uint8_t*>(signature.data()),
|
|
signature.size(), nullptr, &wrapped_private_key_length),
|
|
metrics_, oemcrypto_load_provisioning_, status);
|
|
});
|
|
|
|
if (status != OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
return MapOEMCryptoResult(status, LOAD_PROVISIONING_ERROR,
|
|
"LoadProvisioning");
|
|
}
|
|
|
|
wrapped_private_key->resize(wrapped_private_key_length);
|
|
|
|
WithOecSessionLock("LoadProvisioning Attempt 2", [&] {
|
|
M_TIME(status = OEMCrypto_LoadProvisioning(
|
|
oec_session_id_,
|
|
reinterpret_cast<const uint8_t*>(request.data()), request.size(),
|
|
reinterpret_cast<const uint8_t*>(combined_message.data()),
|
|
combined_message.size(), core_message.size(),
|
|
reinterpret_cast<const uint8_t*>(signature.data()),
|
|
signature.size(),
|
|
reinterpret_cast<uint8_t*>(&wrapped_private_key->front()),
|
|
&wrapped_private_key_length),
|
|
metrics_, oemcrypto_load_provisioning_, status);
|
|
});
|
|
|
|
if (status == OEMCrypto_SUCCESS) {
|
|
wrapped_private_key->resize(wrapped_private_key_length);
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
wrapped_private_key->clear();
|
|
return MapOEMCryptoResult(status, LOAD_PROVISIONING_ERROR,
|
|
"LoadProvisioning");
|
|
}
|
|
|
|
CdmResponseType CryptoSession::LoadProvisioningCast(
|
|
const std::string& derivation_key, const std::string& request,
|
|
const std::string& signed_message, const std::string& core_message,
|
|
const std::string& signature, std::string* wrapped_private_key) {
|
|
LOGV("Loading provisioning certificate: id = %u", oec_session_id_);
|
|
if (wrapped_private_key == nullptr) {
|
|
LOGE("Missing wrapped |wrapped_private_key|");
|
|
return CdmResponseType(PARAMETER_NULL);
|
|
}
|
|
|
|
const std::string combined_message = core_message + signed_message;
|
|
// Round 1, get the size of the wrapped private key buffer.
|
|
size_t wrapped_private_key_length = 0;
|
|
OEMCryptoResult status;
|
|
WithOecSessionLock("LoadProvisioningCast Attempt 1", [&] {
|
|
M_TIME(status = OEMCrypto_LoadProvisioningCast(
|
|
oec_session_id_,
|
|
reinterpret_cast<const uint8_t*>(derivation_key.data()),
|
|
derivation_key.size(),
|
|
reinterpret_cast<const uint8_t*>(request.data()), request.size(),
|
|
reinterpret_cast<const uint8_t*>(combined_message.data()),
|
|
combined_message.size(), core_message.size(),
|
|
reinterpret_cast<const uint8_t*>(signature.data()),
|
|
signature.size(), nullptr, &wrapped_private_key_length),
|
|
metrics_, oemcrypto_load_provisioning_, status);
|
|
});
|
|
|
|
if (status != OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
return MapOEMCryptoResult(status, LOAD_PROVISIONING_ERROR,
|
|
"LoadProvisioningCast");
|
|
}
|
|
|
|
wrapped_private_key->resize(wrapped_private_key_length);
|
|
|
|
WithOecSessionLock("LoadProvisioningCast Attempt 2", [&] {
|
|
M_TIME(status = OEMCrypto_LoadProvisioningCast(
|
|
oec_session_id_,
|
|
reinterpret_cast<const uint8_t*>(derivation_key.data()),
|
|
derivation_key.size(),
|
|
reinterpret_cast<const uint8_t*>(request.data()), request.size(),
|
|
reinterpret_cast<const uint8_t*>(combined_message.data()),
|
|
combined_message.size(), core_message.size(),
|
|
reinterpret_cast<const uint8_t*>(signature.data()),
|
|
signature.size(),
|
|
reinterpret_cast<uint8_t*>(&wrapped_private_key->front()),
|
|
&wrapped_private_key_length),
|
|
metrics_, oemcrypto_load_provisioning_, status);
|
|
});
|
|
|
|
if (status == OEMCrypto_SUCCESS) {
|
|
wrapped_private_key->resize(wrapped_private_key_length);
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
wrapped_private_key->clear();
|
|
return MapOEMCryptoResult(status, LOAD_PROVISIONING_ERROR,
|
|
"LoadProvisioningCast");
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetHdcpCapabilities(HdcpCapability* current,
|
|
HdcpCapability* max) {
|
|
LOGV("Getting HDCP capabilities: id = %u", oec_session_id_);
|
|
RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN);
|
|
return GetHdcpCapabilities(requested_security_level_, current, max);
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetHdcpCapabilities(
|
|
RequestedSecurityLevel security_level, HdcpCapability* current,
|
|
HdcpCapability* max) {
|
|
LOGV("Getting HDCP capabilities: id = %u, security_level = %s",
|
|
oec_session_id_, RequestedSecurityLevelToString(security_level));
|
|
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
|
|
RETURN_IF_NULL(current, PARAMETER_NULL);
|
|
RETURN_IF_NULL(max, PARAMETER_NULL);
|
|
|
|
const OEMCryptoResult status = WithOecReadLock("GetHdcpCapabilities", [&] {
|
|
return OEMCrypto_GetHDCPCapability(security_level, current, max);
|
|
});
|
|
|
|
if (OEMCrypto_SUCCESS == status) {
|
|
metrics_->oemcrypto_current_hdcp_capability_.Record(*current);
|
|
metrics_->oemcrypto_max_hdcp_capability_.Record(*max);
|
|
} else {
|
|
metrics_->oemcrypto_current_hdcp_capability_.SetError(status);
|
|
metrics_->oemcrypto_max_hdcp_capability_.SetError(status);
|
|
}
|
|
|
|
return MapOEMCryptoResult(status, GET_HDCP_CAPABILITY_FAILED,
|
|
"GetHDCPCapability");
|
|
}
|
|
|
|
bool CryptoSession::GetSupportedCertificateTypes(
|
|
SupportedCertificateTypes* support) {
|
|
LOGV("Getting supported certificate types: id = %u", oec_session_id_);
|
|
RETURN_IF_UNINITIALIZED(false);
|
|
RETURN_IF_NULL(support, false);
|
|
const uint32_t oec_support =
|
|
WithOecReadLock("GetSupportedCertificateTypes", [&] {
|
|
return OEMCrypto_SupportedCertificates(requested_security_level_);
|
|
});
|
|
support->rsa_2048_bit = oec_support & OEMCrypto_Supports_RSA_2048bit;
|
|
support->rsa_3072_bit = oec_support & OEMCrypto_Supports_RSA_3072bit;
|
|
support->rsa_cast = oec_support & OEMCrypto_Supports_RSA_CAST;
|
|
support->ecc_secp256r1 = oec_support & OEMCrypto_Supports_ECC_secp256r1;
|
|
support->ecc_secp384r1 = oec_support & OEMCrypto_Supports_ECC_secp384r1;
|
|
support->ecc_secp521r1 = oec_support & OEMCrypto_Supports_ECC_secp521r1;
|
|
return true;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetNumberOfOpenSessions(
|
|
RequestedSecurityLevel security_level, size_t* count) {
|
|
LOGV("Getting number of open sessions: id = %u, security_level = %s",
|
|
oec_session_id_, RequestedSecurityLevelToString(security_level));
|
|
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
|
|
RETURN_IF_NULL(count, PARAMETER_NULL);
|
|
|
|
size_t sessions_count;
|
|
OEMCryptoResult status;
|
|
WithOecReadLock("GetNumberOfOpenSessions", [&] {
|
|
status = OEMCrypto_GetNumberOfOpenSessions(security_level, &sessions_count);
|
|
});
|
|
|
|
if (OEMCrypto_SUCCESS == status) {
|
|
metrics_->oemcrypto_number_of_open_sessions_.Record(sessions_count);
|
|
*count = sessions_count;
|
|
} else {
|
|
metrics_->oemcrypto_number_of_open_sessions_.SetError(status);
|
|
}
|
|
|
|
return MapOEMCryptoResult(status, GET_NUMBER_OF_OPEN_SESSIONS_ERROR,
|
|
"GetNumberOfOpenSessions");
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetMaxNumberOfSessions(
|
|
RequestedSecurityLevel security_level, size_t* max) {
|
|
LOGV("Getting max number of sessions: id = %u, security_level = %s",
|
|
oec_session_id_, RequestedSecurityLevelToString(security_level));
|
|
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
|
|
RETURN_IF_NULL(max, PARAMETER_NULL);
|
|
|
|
size_t max_sessions = 0;
|
|
OEMCryptoResult status;
|
|
WithOecReadLock("GetMaxNumberOfSessions", [&] {
|
|
status = OEMCrypto_GetMaxNumberOfSessions(security_level, &max_sessions);
|
|
});
|
|
|
|
if (OEMCrypto_SUCCESS == status) {
|
|
metrics_->oemcrypto_max_number_of_sessions_.Record(max_sessions);
|
|
*max = max_sessions;
|
|
} else {
|
|
metrics_->oemcrypto_max_number_of_sessions_.SetError(status);
|
|
}
|
|
|
|
return MapOEMCryptoResult(status, GET_MAX_NUMBER_OF_OPEN_SESSIONS_ERROR,
|
|
"GetMaxNumberOfOpenSessions");
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetSrmVersion(uint16_t* srm_version) {
|
|
LOGV("Getting SRM version");
|
|
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
|
|
RETURN_IF_NULL(srm_version, PARAMETER_NULL);
|
|
|
|
const OEMCryptoResult status = WithOecReadLock("GetSrmVersion", [&] {
|
|
return OEMCrypto_GetCurrentSRMVersion(srm_version);
|
|
});
|
|
|
|
// SRM is an optional feature. Whether it is implemented is up to the
|
|
// discretion of OEMs. OEMs may implement this method, but SRM is not
|
|
// required if there is only a local display, as such no SRM version
|
|
// is available/reportable. |srm_version| is only set if SUCCESS is
|
|
// returned.
|
|
switch (status) {
|
|
case OEMCrypto_SUCCESS:
|
|
return CdmResponseType(NO_ERROR);
|
|
case OEMCrypto_LOCAL_DISPLAY_ONLY:
|
|
LOGD("No SRM: Local display only");
|
|
return CRYPTO_ERROR(NO_SRM_VERSION, status);
|
|
case OEMCrypto_ERROR_NOT_IMPLEMENTED:
|
|
LOGD("No SRM: Not implemented");
|
|
return CRYPTO_ERROR(NO_SRM_VERSION, status);
|
|
default:
|
|
return MapOEMCryptoResult(status, GET_SRM_VERSION_ERROR,
|
|
"GetCurrentSRMVersion");
|
|
}
|
|
}
|
|
|
|
bool CryptoSession::GetResourceRatingTier(uint32_t* tier) {
|
|
LOGV("Getting resource rating tier");
|
|
RETURN_IF_NOT_OPEN(false);
|
|
return GetResourceRatingTier(requested_security_level_, tier);
|
|
}
|
|
|
|
bool CryptoSession::GetResourceRatingTier(RequestedSecurityLevel security_level,
|
|
uint32_t* tier) {
|
|
LOGV("Getting resource rating tier: security_level = %s",
|
|
RequestedSecurityLevelToString(security_level));
|
|
RETURN_IF_UNINITIALIZED(false);
|
|
RETURN_IF_NULL(tier, false);
|
|
|
|
WithOecReadLock("GetResourceRatingTier", [&] {
|
|
*tier = OEMCrypto_ResourceRatingTier(security_level);
|
|
metrics_->oemcrypto_resource_rating_tier_.Record(*tier);
|
|
});
|
|
if (*tier < RESOURCE_RATING_TIER_MIN || *tier > RESOURCE_RATING_TIER_MAX) {
|
|
uint32_t api_version;
|
|
if (GetApiVersion(security_level, &api_version)) {
|
|
if (api_version >= OEM_CRYPTO_API_VERSION_SUPPORTS_RESOURCE_RATING_TIER) {
|
|
LOGW("Invalid resource rating tier: %u", *tier);
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool CryptoSession::GetBuildInformation(std::string* info) {
|
|
LOGV("Getting build information");
|
|
RETURN_IF_NOT_OPEN(false);
|
|
return GetBuildInformation(requested_security_level_, info);
|
|
}
|
|
|
|
bool CryptoSession::GetBuildInformation(RequestedSecurityLevel security_level,
|
|
std::string* info) {
|
|
LOGV("Getting build information: security_level = %s",
|
|
RequestedSecurityLevelToString(security_level));
|
|
RETURN_IF_UNINITIALIZED(false);
|
|
RETURN_IF_NULL(info, false);
|
|
size_t info_length = 128;
|
|
info->assign(info_length, '\0');
|
|
OEMCryptoResult result = WithOecReadLock("GetBuildInformation", [&] {
|
|
return OEMCrypto_BuildInformation(&info->front(), &info_length,
|
|
security_level);
|
|
});
|
|
if (result == OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
info->assign(info_length, '\0');
|
|
result = WithOecReadLock("GetBuildInformation Attempt 2", [&] {
|
|
return OEMCrypto_BuildInformation(&info->front(), &info_length,
|
|
security_level);
|
|
});
|
|
}
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("GetBuildInformation failed: result = %d", result);
|
|
info->clear();
|
|
return false;
|
|
}
|
|
info->resize(info_length);
|
|
return true;
|
|
}
|
|
|
|
bool CryptoSession::GetWatermarkingSupport(CdmWatermarkingSupport* support) {
|
|
RETURN_IF_NOT_OPEN(false);
|
|
return GetWatermarkingSupport(requested_security_level_, support);
|
|
}
|
|
|
|
bool CryptoSession::GetWatermarkingSupport(
|
|
RequestedSecurityLevel security_level, CdmWatermarkingSupport* support) {
|
|
LOGV("security_level = %s", RequestedSecurityLevelToString(security_level));
|
|
RETURN_IF_UNINITIALIZED(false);
|
|
RETURN_IF_NULL(support, false);
|
|
const OEMCrypto_WatermarkingSupport oec_support = WithOecReadLock(
|
|
"GetWatermarkingSupport",
|
|
[&] { return OEMCrypto_GetWatermarkingSupport(security_level); });
|
|
switch (oec_support) {
|
|
case OEMCrypto_WatermarkingNotSupported:
|
|
*support = kWatermarkingNotSupported;
|
|
break;
|
|
case OEMCrypto_WatermarkingConfigurable:
|
|
*support = kWatermarkingConfigurable;
|
|
break;
|
|
case OEMCrypto_WatermarkingAlwaysOn:
|
|
*support = kWatermarkingAlwaysOn;
|
|
break;
|
|
case OEMCrypto_WatermarkingError:
|
|
default:
|
|
LOGE("GetWatermarkingSupport error: security_level = %s, result = %d",
|
|
RequestedSecurityLevelToString(security_level),
|
|
static_cast<int>(oec_support));
|
|
metrics_->oemcrypto_watermarking_support_.SetError(oec_support);
|
|
return false;
|
|
}
|
|
metrics_->oemcrypto_watermarking_support_.Record(oec_support);
|
|
return true;
|
|
}
|
|
|
|
bool CryptoSession::GetProductionReadiness(CdmProductionReadiness* readiness) {
|
|
RETURN_IF_NOT_OPEN(false);
|
|
return GetProductionReadiness(requested_security_level_, readiness);
|
|
}
|
|
|
|
bool CryptoSession::GetProductionReadiness(
|
|
RequestedSecurityLevel security_level, CdmProductionReadiness* readiness) {
|
|
LOGV("security_level = %s", RequestedSecurityLevelToString(security_level));
|
|
RETURN_IF_UNINITIALIZED(false);
|
|
RETURN_IF_NULL(readiness, false);
|
|
const OEMCryptoResult result = WithOecReadLock("GetProductionReadiness", [&] {
|
|
return OEMCrypto_ProductionReady(security_level);
|
|
});
|
|
metrics_->oemcrypto_production_readiness_.Record(result);
|
|
switch (result) {
|
|
case OEMCrypto_SUCCESS:
|
|
*readiness = kProductionReadinessTrue;
|
|
break;
|
|
case OEMCrypto_ERROR_NOT_IMPLEMENTED:
|
|
*readiness = kProductionReadinessUnknown;
|
|
break;
|
|
case OEMCrypto_ERROR_UNKNOWN_FAILURE:
|
|
default: // Other vendor-defined codes indicate not production ready.
|
|
LOGD("Not production ready: security_level = %s, result = %d",
|
|
RequestedSecurityLevelToString(security_level),
|
|
static_cast<int>(result));
|
|
*readiness = kProductionReadinessFalse;
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool CryptoSession::GetMaximumUsageTableEntries(
|
|
RequestedSecurityLevel security_level, size_t* number_of_entries) {
|
|
LOGV("Getting maximum usage table entries: security_level = %s",
|
|
RequestedSecurityLevelToString(security_level));
|
|
RETURN_IF_UNINITIALIZED(false);
|
|
if (number_of_entries == nullptr) {
|
|
LOGE("Output parameter |number_of_entries| not provided");
|
|
return false;
|
|
}
|
|
WithOecReadLock("GetMaxUsageTableEntries", [&] {
|
|
*number_of_entries = OEMCrypto_MaximumUsageTableHeaderSize(security_level);
|
|
});
|
|
// Record the number of entries into the metrics.
|
|
metrics_->oemcrypto_maximum_usage_table_header_size_.Record(
|
|
static_cast<uint32_t>(*number_of_entries));
|
|
|
|
if (*number_of_entries == 0) {
|
|
// Special value, indicating that the table size is not directly
|
|
// limited.
|
|
return true;
|
|
}
|
|
return *number_of_entries >= kMinimumUsageTableEntriesSupported;
|
|
}
|
|
|
|
bool CryptoSession::GetDecryptHashSupport(RequestedSecurityLevel security_level,
|
|
uint32_t* decrypt_hash_support) {
|
|
LOGV("Checking if decrypt hash is supported");
|
|
RETURN_IF_UNINITIALIZED(false);
|
|
RETURN_IF_NULL(decrypt_hash_support, false);
|
|
|
|
WithOecReadLock("GetDecryptHashSupport", [&] {
|
|
*decrypt_hash_support = OEMCrypto_SupportsDecryptHash(security_level);
|
|
});
|
|
switch (*decrypt_hash_support) {
|
|
case OEMCrypto_Hash_Not_Supported:
|
|
case OEMCrypto_CRC_Clear_Buffer:
|
|
case OEMCrypto_Partner_Defined_Hash:
|
|
break;
|
|
case OEMCrypto_ERROR_NOT_IMPLEMENTED:
|
|
*decrypt_hash_support = OEMCrypto_Hash_Not_Supported;
|
|
break;
|
|
default:
|
|
// Not flagging an error since it is only used in test
|
|
LOGW("OEMCrypto_SupportsDecryptHash unrecognized result = %d",
|
|
static_cast<int>(*decrypt_hash_support));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::SetDecryptHash(uint32_t frame_number,
|
|
const std::string& hash) {
|
|
LOGV("Setting decrypt hash");
|
|
OEMCryptoResult sts;
|
|
RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN);
|
|
if (hash.size() != sizeof(uint32_t)) {
|
|
LOGE("Unsupported hash size: hash_size = %zu, expected = %zu", hash.size(),
|
|
sizeof(uint32_t));
|
|
return CdmResponseType(UNKNOWN_ERROR);
|
|
}
|
|
WithOecSessionLock("SetDecryptHash", [&] {
|
|
const uint32_t crc32 = *reinterpret_cast<const uint32_t*>(hash.data());
|
|
sts = OEMCrypto_SetDecryptHash(oec_session_id_, frame_number, crc32);
|
|
metrics_->oemcrypto_set_decrypt_hash_.Increment(sts);
|
|
});
|
|
|
|
return MapOEMCryptoResult(sts, SET_DECRYPT_HASH_ERROR, "SetDecryptHash");
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetDecryptHashError(std::string* error_string) {
|
|
LOGV("Getting decrypt hash error");
|
|
RETURN_IF_NULL(error_string, PARAMETER_NULL);
|
|
error_string->clear();
|
|
|
|
uint32_t failed_frame_number = 0;
|
|
OEMCryptoResult sts;
|
|
WithOecSessionLock("GetDecryptHashError", [&] {
|
|
sts = OEMCrypto_GetHashErrorCode(oec_session_id_, &failed_frame_number);
|
|
});
|
|
error_string->assign(std::to_string(sts));
|
|
|
|
if (sts != OEMCrypto_SUCCESS) {
|
|
LOGE("OEMCrypto_GetHashErrorCode failed: status = %d",
|
|
static_cast<int>(sts));
|
|
}
|
|
|
|
switch (sts) {
|
|
case OEMCrypto_SUCCESS:
|
|
case OEMCrypto_ERROR_BAD_HASH:
|
|
error_string->assign(std::to_string(sts));
|
|
error_string->append(",");
|
|
error_string->append(std::to_string(failed_frame_number));
|
|
return CdmResponseType(NO_ERROR);
|
|
case OEMCrypto_ERROR_SESSION_LOST_STATE:
|
|
return CRYPTO_ERROR(SESSION_LOST_STATE_ERROR, sts);
|
|
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
|
|
return CRYPTO_ERROR(SYSTEM_INVALIDATED_ERROR, sts);
|
|
case OEMCrypto_ERROR_UNKNOWN_FAILURE:
|
|
case OEMCrypto_ERROR_NOT_IMPLEMENTED:
|
|
default:
|
|
return CRYPTO_ERROR(GET_DECRYPT_HASH_ERROR, sts);
|
|
}
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GenericEncrypt(const std::string& in_buffer,
|
|
const std::string& key_id,
|
|
const std::string& iv,
|
|
CdmEncryptionAlgorithm algorithm,
|
|
std::string* out_buffer) {
|
|
LOGV("Generic encrypt: id = %u", oec_session_id_);
|
|
RETURN_IF_NULL(out_buffer, PARAMETER_NULL);
|
|
|
|
OEMCrypto_Algorithm oec_algorithm = OEMCrypto_AES_CBC_128_NO_PADDING;
|
|
if (iv.size() != GenericEncryptionBlockSize(algorithm) ||
|
|
!GetGenericEncryptionAlgorithm(algorithm, &oec_algorithm)) {
|
|
return CdmResponseType(INVALID_PARAMETERS_ENG_13);
|
|
}
|
|
|
|
if (out_buffer->size() < in_buffer.size()) {
|
|
out_buffer->resize(in_buffer.size());
|
|
}
|
|
|
|
// TODO(jfore): We need to select a key with a cipher mode and algorithm
|
|
// doesn't seem to fit. Is it ok to just use a default value here?
|
|
// Or do we need to pass it in?
|
|
CdmResponseType result = SelectKey(key_id, kCipherModeCbc);
|
|
if (result != NO_ERROR) return result;
|
|
|
|
OEMCryptoResult sts;
|
|
|
|
WithOecSessionLock("GenericEncrypt", [&] {
|
|
sts =
|
|
key_session_->GenericEncrypt(in_buffer, iv, oec_algorithm, out_buffer);
|
|
});
|
|
|
|
if (OEMCrypto_SUCCESS != sts) {
|
|
LOGE("OEMCrypto_Generic_Encrypt failed: status = %d",
|
|
static_cast<int>(sts));
|
|
}
|
|
|
|
switch (sts) {
|
|
case OEMCrypto_SUCCESS:
|
|
return CdmResponseType(NO_ERROR);
|
|
case OEMCrypto_ERROR_KEY_EXPIRED:
|
|
return CRYPTO_ERROR(NEED_KEY, sts);
|
|
case OEMCrypto_ERROR_NO_CONTENT_KEY:
|
|
case OEMCrypto_KEY_NOT_LOADED: // obsolete in v15.
|
|
return CRYPTO_ERROR(KEY_NOT_FOUND_3, sts);
|
|
case OEMCrypto_ERROR_OUTPUT_TOO_LARGE:
|
|
return CRYPTO_ERROR(OUTPUT_TOO_LARGE_ERROR, sts);
|
|
case OEMCrypto_ERROR_SESSION_LOST_STATE:
|
|
return CRYPTO_ERROR(SESSION_LOST_STATE_ERROR, sts);
|
|
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
|
|
return CRYPTO_ERROR(SYSTEM_INVALIDATED_ERROR, sts);
|
|
default:
|
|
return CRYPTO_ERROR(UNKNOWN_ERROR, sts);
|
|
}
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GenericDecrypt(const std::string& in_buffer,
|
|
const std::string& key_id,
|
|
const std::string& iv,
|
|
CdmEncryptionAlgorithm algorithm,
|
|
std::string* out_buffer) {
|
|
LOGV("Generic decrypt: id = %u", oec_session_id_);
|
|
RETURN_IF_NULL(out_buffer, PARAMETER_NULL);
|
|
|
|
OEMCrypto_Algorithm oec_algorithm = OEMCrypto_AES_CBC_128_NO_PADDING;
|
|
if (iv.size() != GenericEncryptionBlockSize(algorithm) ||
|
|
!GetGenericEncryptionAlgorithm(algorithm, &oec_algorithm)) {
|
|
return CdmResponseType(INVALID_PARAMETERS_ENG_14);
|
|
}
|
|
|
|
if (out_buffer->size() < in_buffer.size()) {
|
|
out_buffer->resize(in_buffer.size());
|
|
}
|
|
|
|
// TODO(jfore): We need to select a key with a cipher mode and algorithm
|
|
// doesn't seem to fit. Is it ok to just use a default value here?
|
|
// Or do we need to pass it in?
|
|
CdmResponseType result = SelectKey(key_id, kCipherModeCbc);
|
|
if (result != NO_ERROR) return result;
|
|
|
|
OEMCryptoResult sts;
|
|
|
|
WithOecSessionLock("GenericDecrypt", [&] {
|
|
sts =
|
|
key_session_->GenericDecrypt(in_buffer, iv, oec_algorithm, out_buffer);
|
|
});
|
|
|
|
if (OEMCrypto_SUCCESS != sts) {
|
|
LOGE("OEMCrypto_Generic_Decrypt failed: status = %d",
|
|
static_cast<int>(sts));
|
|
}
|
|
|
|
switch (sts) {
|
|
case OEMCrypto_SUCCESS:
|
|
return CdmResponseType(NO_ERROR);
|
|
case OEMCrypto_ERROR_KEY_EXPIRED:
|
|
return CRYPTO_ERROR(NEED_KEY, sts);
|
|
case OEMCrypto_ERROR_NO_CONTENT_KEY:
|
|
case OEMCrypto_KEY_NOT_LOADED: // obsolete in v15.
|
|
return CRYPTO_ERROR(KEY_NOT_FOUND_4, sts);
|
|
case OEMCrypto_ERROR_OUTPUT_TOO_LARGE:
|
|
return CRYPTO_ERROR(OUTPUT_TOO_LARGE_ERROR, sts);
|
|
case OEMCrypto_ERROR_SESSION_LOST_STATE:
|
|
return CRYPTO_ERROR(SESSION_LOST_STATE_ERROR, sts);
|
|
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
|
|
return CRYPTO_ERROR(SYSTEM_INVALIDATED_ERROR, sts);
|
|
default:
|
|
return CRYPTO_ERROR(UNKNOWN_ERROR, sts);
|
|
}
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GenericSign(const std::string& message,
|
|
const std::string& key_id,
|
|
CdmSigningAlgorithm algorithm,
|
|
std::string* signature) {
|
|
LOGV("Generic sign: id = %u", oec_session_id_);
|
|
RETURN_IF_NULL(signature, PARAMETER_NULL);
|
|
|
|
OEMCrypto_Algorithm oec_algorithm = OEMCrypto_HMAC_SHA256;
|
|
if (!GetGenericSigningAlgorithm(algorithm, &oec_algorithm)) {
|
|
return CdmResponseType(INVALID_PARAMETERS_ENG_15);
|
|
}
|
|
|
|
// TODO(jfore): We need to select a key with a cipher mode and algorithm
|
|
// doesn't seem to fit. Is it ok to just use a default value here?
|
|
// Or do we need to pass it in?
|
|
CdmResponseType result = SelectKey(key_id, kCipherModeCbc);
|
|
if (result != NO_ERROR) return result;
|
|
|
|
OEMCryptoResult sts;
|
|
WithOecSessionLock("GenericSign", [&] {
|
|
sts = key_session_->GenericSign(message, oec_algorithm, signature);
|
|
});
|
|
|
|
switch (sts) {
|
|
case OEMCrypto_SUCCESS:
|
|
return CdmResponseType(NO_ERROR);
|
|
case OEMCrypto_ERROR_KEY_EXPIRED:
|
|
return CRYPTO_ERROR(NEED_KEY, sts);
|
|
case OEMCrypto_ERROR_NO_CONTENT_KEY:
|
|
case OEMCrypto_KEY_NOT_LOADED: // obsolete in v15.
|
|
return CRYPTO_ERROR(KEY_NOT_FOUND_5, sts);
|
|
case OEMCrypto_ERROR_OUTPUT_TOO_LARGE:
|
|
return CRYPTO_ERROR(OUTPUT_TOO_LARGE_ERROR, sts);
|
|
case OEMCrypto_ERROR_SESSION_LOST_STATE:
|
|
return CRYPTO_ERROR(SESSION_LOST_STATE_ERROR, sts);
|
|
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
|
|
return CRYPTO_ERROR(SYSTEM_INVALIDATED_ERROR, sts);
|
|
default:
|
|
return CRYPTO_ERROR(UNKNOWN_ERROR, sts);
|
|
}
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GenericVerify(const std::string& message,
|
|
const std::string& key_id,
|
|
CdmSigningAlgorithm algorithm,
|
|
const std::string& signature) {
|
|
LOGV("Generic verify: id = %u", oec_session_id_);
|
|
|
|
OEMCrypto_Algorithm oec_algorithm = OEMCrypto_HMAC_SHA256;
|
|
if (!GetGenericSigningAlgorithm(algorithm, &oec_algorithm)) {
|
|
return CdmResponseType(INVALID_PARAMETERS_ENG_16);
|
|
}
|
|
|
|
// TODO(jfore): We need to select a key with a cipher mode and algorithm
|
|
// doesn't seem to fit. Is it ok to just use a default value here?
|
|
// Or do we need to pass it in?
|
|
CdmResponseType result = SelectKey(key_id, kCipherModeCbc);
|
|
if (result != NO_ERROR) return result;
|
|
|
|
OEMCryptoResult sts;
|
|
WithOecSessionLock("GenericVerify", [&] {
|
|
sts = key_session_->GenericVerify(message, oec_algorithm, signature);
|
|
});
|
|
|
|
if (OEMCrypto_SUCCESS != sts) {
|
|
LOGE("OEMCrypto_Generic_Verify failed: status = %d", static_cast<int>(sts));
|
|
}
|
|
|
|
switch (sts) {
|
|
case OEMCrypto_SUCCESS:
|
|
return CdmResponseType(NO_ERROR);
|
|
case OEMCrypto_ERROR_KEY_EXPIRED:
|
|
return CRYPTO_ERROR(NEED_KEY, sts);
|
|
case OEMCrypto_ERROR_NO_CONTENT_KEY:
|
|
case OEMCrypto_KEY_NOT_LOADED: // obsolete in v15.
|
|
return CRYPTO_ERROR(KEY_NOT_FOUND_6, sts);
|
|
case OEMCrypto_ERROR_OUTPUT_TOO_LARGE:
|
|
return CRYPTO_ERROR(OUTPUT_TOO_LARGE_ERROR, sts);
|
|
case OEMCrypto_ERROR_SESSION_LOST_STATE:
|
|
return CRYPTO_ERROR(SESSION_LOST_STATE_ERROR, sts);
|
|
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
|
|
return CRYPTO_ERROR(SYSTEM_INVALIDATED_ERROR, sts);
|
|
default:
|
|
return CRYPTO_ERROR(UNKNOWN_ERROR, sts);
|
|
}
|
|
}
|
|
|
|
CdmResponseType CryptoSession::CreateUsageTableHeader(
|
|
RequestedSecurityLevel requested_security_level,
|
|
UsageTableHeader* usage_table_header) {
|
|
LOGV("Creating usage table header: requested_security_level = %s",
|
|
RequestedSecurityLevelToString(requested_security_level));
|
|
RETURN_IF_NULL(usage_table_header, PARAMETER_NULL);
|
|
|
|
usage_table_header->resize(kEstimatedInitialUsageTableHeader);
|
|
size_t usage_table_header_size = usage_table_header->size();
|
|
OEMCryptoResult result;
|
|
WithOecWriteLock("CreateUsageTableHeader Attempt 1", [&] {
|
|
result = OEMCrypto_CreateUsageTableHeader(
|
|
requested_security_level,
|
|
reinterpret_cast<uint8_t*>(
|
|
const_cast<char*>(usage_table_header->data())),
|
|
&usage_table_header_size);
|
|
metrics_->oemcrypto_create_usage_table_header_.Increment(result);
|
|
});
|
|
|
|
if (result == OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
usage_table_header->resize(usage_table_header_size);
|
|
WithOecWriteLock("CreateUsageTableHeader Attempt 2", [&] {
|
|
result = OEMCrypto_CreateUsageTableHeader(
|
|
requested_security_level,
|
|
reinterpret_cast<uint8_t*>(
|
|
const_cast<char*>(usage_table_header->data())),
|
|
&usage_table_header_size);
|
|
metrics_->oemcrypto_create_usage_table_header_.Increment(result);
|
|
});
|
|
}
|
|
|
|
switch (result) {
|
|
case OEMCrypto_SUCCESS:
|
|
usage_table_header->resize(usage_table_header_size);
|
|
return CdmResponseType(NO_ERROR);
|
|
default:
|
|
return MapOEMCryptoResult(result, CREATE_USAGE_TABLE_ERROR,
|
|
"CreateUsageTableHeader");
|
|
}
|
|
}
|
|
|
|
CdmResponseType CryptoSession::LoadUsageTableHeader(
|
|
RequestedSecurityLevel requested_security_level,
|
|
const UsageTableHeader& usage_table_header) {
|
|
LOGV("Loading usage table header: requested_security_level = %s",
|
|
RequestedSecurityLevelToString(requested_security_level));
|
|
|
|
const OEMCryptoResult result = WithOecWriteLock("LoadUsageTableHeader", [&] {
|
|
return OEMCrypto_LoadUsageTableHeader(
|
|
requested_security_level,
|
|
reinterpret_cast<const uint8_t*>(usage_table_header.data()),
|
|
usage_table_header.size());
|
|
});
|
|
metrics_->oemcrypto_load_usage_table_header_.Increment(result);
|
|
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
if (result == OEMCrypto_WARNING_GENERATION_SKEW) {
|
|
LOGW("OEMCrypto_LoadUsageTableHeader warning: generation skew");
|
|
} else {
|
|
LOGE("OEMCrypto_LoadUsageTableHeader failed: status = %d",
|
|
static_cast<int>(result));
|
|
}
|
|
}
|
|
|
|
switch (result) {
|
|
case OEMCrypto_SUCCESS:
|
|
case OEMCrypto_WARNING_GENERATION_SKEW:
|
|
return CdmResponseType(NO_ERROR);
|
|
case OEMCrypto_ERROR_GENERATION_SKEW:
|
|
return CRYPTO_ERROR(LOAD_USAGE_HEADER_GENERATION_SKEW, result);
|
|
case OEMCrypto_ERROR_SIGNATURE_FAILURE:
|
|
return CRYPTO_ERROR(LOAD_USAGE_HEADER_SIGNATURE_FAILURE, result);
|
|
case OEMCrypto_ERROR_BAD_MAGIC:
|
|
return CRYPTO_ERROR(LOAD_USAGE_HEADER_BAD_MAGIC, result);
|
|
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
|
|
return CRYPTO_ERROR(SYSTEM_INVALIDATED_ERROR, result);
|
|
case OEMCrypto_ERROR_UNKNOWN_FAILURE:
|
|
default:
|
|
return CRYPTO_ERROR(LOAD_USAGE_HEADER_UNKNOWN_ERROR, result);
|
|
}
|
|
}
|
|
|
|
CdmResponseType CryptoSession::ShrinkUsageTableHeader(
|
|
RequestedSecurityLevel requested_security_level, uint32_t new_entry_count,
|
|
UsageTableHeader* usage_table_header) {
|
|
LOGV("Shrinking usage table header: requested_security_level = %s",
|
|
RequestedSecurityLevelToString(requested_security_level));
|
|
RETURN_IF_NULL(usage_table_header, PARAMETER_NULL);
|
|
|
|
size_t usage_table_header_len = 0;
|
|
OEMCryptoResult result;
|
|
WithOecWriteLock("ShrinkUsageTableHeader Attempt 1", [&] {
|
|
result = OEMCrypto_ShrinkUsageTableHeader(requested_security_level,
|
|
new_entry_count, nullptr,
|
|
&usage_table_header_len);
|
|
metrics_->oemcrypto_shrink_usage_table_header_.Increment(result);
|
|
});
|
|
|
|
if (result == OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
usage_table_header->resize(usage_table_header_len);
|
|
WithOecWriteLock("ShrinkUsageTableHeader Attempt 2", [&] {
|
|
result = OEMCrypto_ShrinkUsageTableHeader(
|
|
requested_security_level, new_entry_count,
|
|
reinterpret_cast<uint8_t*>(
|
|
const_cast<char*>(usage_table_header->data())),
|
|
&usage_table_header_len);
|
|
metrics_->oemcrypto_shrink_usage_table_header_.Increment(result);
|
|
});
|
|
}
|
|
|
|
switch (result) {
|
|
case OEMCrypto_SUCCESS:
|
|
usage_table_header->resize(usage_table_header_len);
|
|
return CdmResponseType(NO_ERROR);
|
|
case OEMCrypto_ERROR_ENTRY_IN_USE:
|
|
return CRYPTO_ERROR(SHRINK_USAGE_TABLE_HEADER_ENTRY_IN_USE, result);
|
|
default:
|
|
return MapOEMCryptoResult(result, SHRINK_USAGE_TABLE_HEADER_UNKNOWN_ERROR,
|
|
"ShrinkUsageTableHeader");
|
|
}
|
|
}
|
|
|
|
CdmResponseType CryptoSession::CreateUsageEntry(UsageEntryIndex* entry_index) {
|
|
LOGV("Creating usage entry: id = %u", oec_session_id_);
|
|
|
|
RETURN_IF_NULL(entry_index, PARAMETER_NULL);
|
|
|
|
OEMCryptoResult result;
|
|
WithOecWriteLock("CreateUsageEntry", [&] {
|
|
result = OEMCrypto_CreateNewUsageEntry(oec_session_id_, entry_index);
|
|
metrics_->oemcrypto_create_new_usage_entry_.Increment(result);
|
|
});
|
|
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("OEMCrypto_CreateNewUsageEntry failed: status = %d",
|
|
static_cast<int>(result));
|
|
}
|
|
|
|
switch (result) {
|
|
case OEMCrypto_SUCCESS:
|
|
return CdmResponseType(NO_ERROR);
|
|
case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES:
|
|
return CRYPTO_ERROR(INSUFFICIENT_CRYPTO_RESOURCES, result);
|
|
case OEMCrypto_ERROR_SESSION_LOST_STATE:
|
|
return CRYPTO_ERROR(SESSION_LOST_STATE_ERROR, result);
|
|
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
|
|
return CRYPTO_ERROR(SYSTEM_INVALIDATED_ERROR, result);
|
|
default:
|
|
return CRYPTO_ERROR(CREATE_USAGE_ENTRY_UNKNOWN_ERROR, result);
|
|
}
|
|
}
|
|
|
|
CdmResponseType CryptoSession::LoadUsageEntry(UsageEntryIndex entry_index,
|
|
const UsageEntry& usage_entry) {
|
|
LOGV("Loading usage entry: id = %u", oec_session_id_);
|
|
|
|
OEMCryptoResult result;
|
|
WithOecWriteLock("LoadUsageEntry", [&] {
|
|
result = OEMCrypto_LoadUsageEntry(
|
|
oec_session_id_, entry_index,
|
|
reinterpret_cast<const uint8_t*>(usage_entry.data()),
|
|
usage_entry.size());
|
|
metrics_->oemcrypto_load_usage_entry_.Increment(result);
|
|
});
|
|
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
if (result == OEMCrypto_WARNING_GENERATION_SKEW) {
|
|
LOGW("OEMCrypto_LoadUsageEntry warning: generation skew");
|
|
} else {
|
|
LOGE("OEMCrypto_LoadUsageEntry failed: status = %d",
|
|
static_cast<int>(result));
|
|
}
|
|
}
|
|
|
|
switch (result) {
|
|
case OEMCrypto_SUCCESS:
|
|
case OEMCrypto_WARNING_GENERATION_SKEW:
|
|
return CdmResponseType(NO_ERROR);
|
|
case OEMCrypto_ERROR_INVALID_SESSION:
|
|
// This case is special, as it could imply that the provided
|
|
// session ID is invalid (CDM internal bug), or that the entry
|
|
// being loaded is already in use in a different session.
|
|
// It is up to the caller to handle this.
|
|
return CRYPTO_ERROR(LOAD_USAGE_ENTRY_INVALID_SESSION, result);
|
|
case OEMCrypto_ERROR_GENERATION_SKEW:
|
|
return CRYPTO_ERROR(LOAD_USAGE_ENTRY_GENERATION_SKEW, result);
|
|
case OEMCrypto_ERROR_SIGNATURE_FAILURE:
|
|
return CRYPTO_ERROR(LOAD_USAGE_ENTRY_SIGNATURE_FAILURE, result);
|
|
default:
|
|
return MapOEMCryptoResult(result, LOAD_USAGE_ENTRY_UNKNOWN_ERROR,
|
|
"LoadUsageEntry");
|
|
}
|
|
}
|
|
|
|
CdmResponseType CryptoSession::UpdateUsageEntry(
|
|
UsageTableHeader* usage_table_header, UsageEntry* usage_entry) {
|
|
LOGV("Updating usage entry: id = %u", oec_session_id_);
|
|
|
|
RETURN_IF_NULL(usage_table_header, PARAMETER_NULL);
|
|
RETURN_IF_NULL(usage_entry, PARAMETER_NULL);
|
|
|
|
size_t usage_table_header_len = 0;
|
|
size_t usage_entry_len = 0;
|
|
OEMCryptoResult result;
|
|
WithOecWriteLock("UpdateUsageEntry Attempt 1", [&] {
|
|
result = OEMCrypto_UpdateUsageEntry(oec_session_id_, nullptr,
|
|
&usage_table_header_len, nullptr,
|
|
&usage_entry_len);
|
|
});
|
|
metrics_->oemcrypto_update_usage_entry_.Increment(result);
|
|
|
|
if (result == OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
usage_table_header->resize(usage_table_header_len);
|
|
usage_entry->resize(usage_entry_len);
|
|
|
|
WithOecWriteLock("UpdateUsageEntry Attempt 2", [&] {
|
|
result = OEMCrypto_UpdateUsageEntry(
|
|
oec_session_id_,
|
|
reinterpret_cast<uint8_t*>(
|
|
const_cast<char*>(usage_table_header->data())),
|
|
&usage_table_header_len,
|
|
reinterpret_cast<uint8_t*>(const_cast<char*>(usage_entry->data())),
|
|
&usage_entry_len);
|
|
});
|
|
metrics_->oemcrypto_update_usage_entry_.Increment(result);
|
|
}
|
|
|
|
if (result == OEMCrypto_SUCCESS) {
|
|
usage_table_header->resize(usage_table_header_len);
|
|
usage_entry->resize(usage_entry_len);
|
|
}
|
|
|
|
return MapOEMCryptoResult(result, UPDATE_USAGE_ENTRY_UNKNOWN_ERROR,
|
|
"UpdateUsageEntry");
|
|
}
|
|
|
|
CdmResponseType CryptoSession::MoveUsageEntry(UsageEntryIndex new_entry_index) {
|
|
LOGV("Moving usage entry: id = %u", oec_session_id_);
|
|
|
|
OEMCryptoResult result;
|
|
WithOecWriteLock("MoveUsageEntry", [&] {
|
|
result = OEMCrypto_MoveEntry(oec_session_id_, new_entry_index);
|
|
metrics_->oemcrypto_move_entry_.Increment(result);
|
|
});
|
|
|
|
switch (result) {
|
|
case OEMCrypto_ERROR_ENTRY_IN_USE:
|
|
LOGW("OEMCrypto_MoveEntry failed: Destination index in use: index = %u",
|
|
new_entry_index);
|
|
return CRYPTO_ERROR(MOVE_USAGE_ENTRY_DESTINATION_IN_USE, result);
|
|
default:
|
|
return MapOEMCryptoResult(result, MOVE_USAGE_ENTRY_UNKNOWN_ERROR,
|
|
"MoveUsageEntry");
|
|
}
|
|
}
|
|
|
|
bool CryptoSession::GetAnalogOutputCapabilities(bool* can_support_output,
|
|
bool* can_disable_output,
|
|
bool* can_support_cgms_a) {
|
|
LOGV("Getting analog output capabilities: id = %u", oec_session_id_);
|
|
RETURN_IF_UNINITIALIZED(false);
|
|
const uint32_t flags = WithOecReadLock("GetAnalogOutputCapabilities", [&] {
|
|
return OEMCrypto_GetAnalogOutputFlags(requested_security_level_);
|
|
});
|
|
|
|
if ((flags & OEMCrypto_Unknown_Analog_Output) != 0) return false;
|
|
*can_support_cgms_a = flags & OEMCrypto_Supports_CGMS_A;
|
|
*can_support_output = flags & OEMCrypto_Supports_Analog_Output;
|
|
*can_disable_output = flags & OEMCrypto_Can_Disable_Analog_Ouptput;
|
|
return true;
|
|
}
|
|
|
|
OEMCryptoResult CryptoSession::DecryptMultipleSamples(
|
|
const std::vector<OEMCrypto_SampleDescription>& samples,
|
|
CdmCipherMode cipher_mode, const OEMCrypto_CENCEncryptPatternDesc& pattern,
|
|
bool is_any_subsample_protected) {
|
|
OEMCryptoResult sts = OEMCrypto_ERROR_BUFFER_TOO_LARGE;
|
|
|
|
// If there's only one sample, automatically fall through to avoid a redundant
|
|
// roundtrip through OEMCrypto_DecryptCENC()
|
|
if (samples.size() > 1) {
|
|
WithOecSessionLock("DecryptMultipleSamples", [&] {
|
|
sts = key_session_->Decrypt(samples.data(), samples.size(), pattern,
|
|
is_any_subsample_protected);
|
|
});
|
|
}
|
|
|
|
if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE) {
|
|
// Fall back to sending each sample individually
|
|
for (const OEMCrypto_SampleDescription& sample : samples) {
|
|
sts = DecryptSample(sample, cipher_mode, pattern,
|
|
is_any_subsample_protected);
|
|
if (sts != OEMCrypto_SUCCESS) break;
|
|
}
|
|
}
|
|
|
|
return sts;
|
|
}
|
|
|
|
OEMCryptoResult CryptoSession::DecryptSample(
|
|
const OEMCrypto_SampleDescription& sample, CdmCipherMode cipher_mode,
|
|
const OEMCrypto_CENCEncryptPatternDesc& pattern,
|
|
bool is_any_subsample_protected) {
|
|
OEMCryptoResult sts = OEMCrypto_ERROR_BUFFER_TOO_LARGE;
|
|
|
|
// If there's only one subsample and it contains only one type of region,
|
|
// automatically fall through to avoid a redundant roundtrip through
|
|
// OEMCrypto_DecryptCENC()
|
|
if (sample.subsamples_length > 1 ||
|
|
(sample.subsamples[0].num_bytes_clear > 0 &&
|
|
sample.subsamples[0].num_bytes_encrypted > 0)) {
|
|
WithOecSessionLock("DecryptSample", [&] {
|
|
sts = key_session_->Decrypt(&sample, 1, pattern,
|
|
is_any_subsample_protected);
|
|
});
|
|
}
|
|
|
|
if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE) {
|
|
// Fall back to sending each subsample region individually
|
|
sts = OEMCrypto_SUCCESS;
|
|
OEMCrypto_SampleDescription fake_sample = sample;
|
|
for (size_t i = 0; i < sample.subsamples_length; ++i) {
|
|
const OEMCrypto_SubSampleDescription& original_subsample =
|
|
sample.subsamples[i];
|
|
|
|
if (original_subsample.num_bytes_clear > 0) {
|
|
const size_t length = original_subsample.num_bytes_clear;
|
|
OEMCrypto_SubSampleDescription clear_subsample{
|
|
length,
|
|
0, // num_bytes_encrypted
|
|
0, // subsample_flags
|
|
0 // block_offset, not relevant for clear data
|
|
};
|
|
if (original_subsample.subsample_flags & OEMCrypto_FirstSubsample) {
|
|
clear_subsample.subsample_flags |= OEMCrypto_FirstSubsample;
|
|
}
|
|
if ((original_subsample.subsample_flags & OEMCrypto_LastSubsample) &&
|
|
original_subsample.num_bytes_encrypted == 0) {
|
|
clear_subsample.subsample_flags |= OEMCrypto_LastSubsample;
|
|
}
|
|
|
|
fake_sample.buffers.input_data_length = length;
|
|
fake_sample.subsamples = &clear_subsample;
|
|
fake_sample.subsamples_length = 1;
|
|
|
|
sts = LegacyDecrypt(fake_sample, cipher_mode, pattern,
|
|
is_any_subsample_protected);
|
|
if (sts != OEMCrypto_SUCCESS) break;
|
|
|
|
fake_sample.buffers.input_data += length;
|
|
AdvanceDestBuffer(&fake_sample.buffers.output_descriptor, length);
|
|
}
|
|
|
|
if (original_subsample.num_bytes_encrypted > 0) {
|
|
const size_t length = original_subsample.num_bytes_encrypted;
|
|
OEMCrypto_SubSampleDescription encrypted_subsample{
|
|
0, // num_bytes_clear
|
|
length,
|
|
0, // subsample_flags
|
|
original_subsample.block_offset};
|
|
if ((original_subsample.subsample_flags & OEMCrypto_FirstSubsample) &&
|
|
original_subsample.num_bytes_clear == 0) {
|
|
encrypted_subsample.subsample_flags |= OEMCrypto_FirstSubsample;
|
|
}
|
|
if (original_subsample.subsample_flags & OEMCrypto_LastSubsample) {
|
|
encrypted_subsample.subsample_flags |= OEMCrypto_LastSubsample;
|
|
}
|
|
|
|
fake_sample.buffers.input_data_length = length;
|
|
fake_sample.subsamples = &encrypted_subsample;
|
|
fake_sample.subsamples_length = 1;
|
|
|
|
sts = LegacyDecrypt(fake_sample, cipher_mode, pattern,
|
|
is_any_subsample_protected);
|
|
if (sts != OEMCrypto_SUCCESS) break;
|
|
|
|
fake_sample.buffers.input_data += length;
|
|
AdvanceDestBuffer(&fake_sample.buffers.output_descriptor, length);
|
|
if (cipher_mode == kCipherModeCtr) {
|
|
wvutil::AdvanceIvCtr(&fake_sample.iv,
|
|
original_subsample.block_offset +
|
|
original_subsample.num_bytes_encrypted);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return sts;
|
|
}
|
|
|
|
OEMCryptoResult CryptoSession::LegacyDecrypt(
|
|
const OEMCrypto_SampleDescription& sample, CdmCipherMode cipher_mode,
|
|
const OEMCrypto_CENCEncryptPatternDesc& pattern,
|
|
bool is_any_subsample_protected) {
|
|
const size_t max_chunk_size = GetMaxSubsampleRegionSize();
|
|
OEMCryptoResult sts = OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
|
|
|
// We can be sure this is only called with one subsample containing one
|
|
// region of data.
|
|
const OEMCrypto_SubSampleDescription& subsample = sample.subsamples[0];
|
|
const bool is_encrypted = (subsample.num_bytes_encrypted > 0);
|
|
const bool is_only_subsample =
|
|
subsample.subsample_flags ==
|
|
(OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample);
|
|
if (!is_encrypted && is_only_subsample) {
|
|
WithOecSessionLock("LegacyDecrypt() calling CopyBuffer", [&] {
|
|
M_TIME(sts = OEMCrypto_CopyBuffer(
|
|
oec_session_id_, sample.buffers.input_data,
|
|
sample.buffers.input_data_length,
|
|
&sample.buffers.output_descriptor, subsample.subsample_flags),
|
|
metrics_, oemcrypto_copy_buffer_, sts,
|
|
metrics::Pow2Bucket(sample.buffers.input_data_length));
|
|
});
|
|
|
|
if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE &&
|
|
sample.buffers.input_data_length > max_chunk_size) {
|
|
// OEMCrypto_CopyBuffer rejected the buffer as too large, so chunk it up
|
|
// into 100 KiB sections.
|
|
sts = LegacyCopyBufferInChunks(sample, max_chunk_size);
|
|
}
|
|
}
|
|
if (is_encrypted || sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) {
|
|
WithOecSessionLock("LegacyDecrypt() calling key_session_->Decrypt()", [&] {
|
|
sts = key_session_->Decrypt(&sample, 1, pattern,
|
|
is_any_subsample_protected);
|
|
});
|
|
|
|
if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE) {
|
|
// OEMCrypto_DecryptCENC rejected the buffer as too large, so chunk it
|
|
// up into sections no more than 100 KiB. The exact chunk size needs to
|
|
// be an even number of pattern repetitions long or else the pattern
|
|
// will get out of sync.
|
|
const size_t pattern_length =
|
|
(pattern.encrypt + pattern.skip) * kAes128BlockSize;
|
|
const size_t chunk_size =
|
|
pattern_length > 0
|
|
? max_chunk_size - (max_chunk_size % pattern_length)
|
|
: max_chunk_size;
|
|
|
|
if (sample.buffers.input_data_length > chunk_size) {
|
|
sts = LegacyDecryptInChunks(sample, cipher_mode, pattern, chunk_size,
|
|
is_any_subsample_protected);
|
|
}
|
|
}
|
|
}
|
|
|
|
return sts;
|
|
}
|
|
|
|
OEMCryptoResult CryptoSession::LegacyCopyBufferInChunks(
|
|
const OEMCrypto_SampleDescription& sample, size_t max_chunk_size) {
|
|
const uint8_t* input_data = sample.buffers.input_data;
|
|
size_t remaining_input_data = sample.buffers.input_data_length;
|
|
OEMCrypto_DestBufferDesc output_descriptor = sample.buffers.output_descriptor;
|
|
uint8_t subsample_flags = OEMCrypto_FirstSubsample;
|
|
OEMCryptoResult sts = OEMCrypto_SUCCESS;
|
|
|
|
while (remaining_input_data > 0) {
|
|
// Calculate the size of the next chunk.
|
|
const size_t chunk_size = std::min(remaining_input_data, max_chunk_size);
|
|
|
|
// Re-add "last subsample" flag if this is the last subsample.
|
|
if (chunk_size == remaining_input_data) {
|
|
subsample_flags |= OEMCrypto_LastSubsample;
|
|
}
|
|
|
|
WithOecSessionLock("LegacyCopyBufferInChunks", [&] {
|
|
M_TIME(sts = OEMCrypto_CopyBuffer(oec_session_id_, input_data, chunk_size,
|
|
&output_descriptor, subsample_flags),
|
|
metrics_, oemcrypto_copy_buffer_, sts,
|
|
metrics::Pow2Bucket(chunk_size));
|
|
});
|
|
|
|
if (sts != OEMCrypto_SUCCESS) break;
|
|
|
|
// Clear any subsample flags before the next loop iteration.
|
|
subsample_flags = 0;
|
|
|
|
// Update the source and destination buffers based on the amount of data
|
|
// copied.
|
|
input_data += chunk_size;
|
|
remaining_input_data -= chunk_size;
|
|
AdvanceDestBuffer(&output_descriptor, chunk_size);
|
|
}
|
|
|
|
return sts;
|
|
}
|
|
|
|
OEMCryptoResult CryptoSession::LegacyDecryptInChunks(
|
|
const OEMCrypto_SampleDescription& sample, CdmCipherMode cipher_mode,
|
|
const OEMCrypto_CENCEncryptPatternDesc& pattern, size_t max_chunk_size,
|
|
bool is_any_subsample_protected) {
|
|
const OEMCrypto_SubSampleDescription& subsample = sample.subsamples[0];
|
|
|
|
const bool is_protected = (subsample.num_bytes_encrypted > 0);
|
|
OEMCrypto_SampleDescription fake_sample = sample;
|
|
OEMCrypto_SubSampleDescription fake_subsample = subsample;
|
|
fake_sample.subsamples = &fake_subsample;
|
|
fake_subsample.subsample_flags =
|
|
subsample.subsample_flags & OEMCrypto_FirstSubsample;
|
|
|
|
size_t remaining_input_data = sample.buffers.input_data_length;
|
|
OEMCryptoResult sts = OEMCrypto_SUCCESS;
|
|
|
|
while (remaining_input_data > 0) {
|
|
// Calculate the size of the next chunk.
|
|
const size_t chunk_size = std::min(remaining_input_data, max_chunk_size);
|
|
fake_sample.buffers.input_data_length = chunk_size;
|
|
if (is_protected) {
|
|
fake_subsample.num_bytes_encrypted = chunk_size;
|
|
} else {
|
|
fake_subsample.num_bytes_clear = chunk_size;
|
|
}
|
|
|
|
// Re-add "last subsample" flag if this is the last subsample.
|
|
if (chunk_size == remaining_input_data) {
|
|
fake_subsample.subsample_flags |=
|
|
subsample.subsample_flags & OEMCrypto_LastSubsample;
|
|
}
|
|
|
|
// |pattern| and |fake_subsample.block_offset| do not need to change because
|
|
// |max_chunk_size| is guaranteed to be an even multiple of the
|
|
// pattern length long, which is also guaranteed to be an exact number
|
|
// of AES blocks long.
|
|
WithOecSessionLock("LegacyDecryptInChunks", [&] {
|
|
sts = key_session_->Decrypt(&fake_sample, 1, pattern,
|
|
is_any_subsample_protected);
|
|
});
|
|
|
|
if (sts != OEMCrypto_SUCCESS) break;
|
|
|
|
// Clear any subsample flags before the next loop iteration.
|
|
fake_subsample.subsample_flags = 0;
|
|
|
|
// Update the IV so that it is valid for the next iteration. This should not
|
|
// be done on the last iteration both to save time and because the 'cbcs'
|
|
// calculation can underflow if the chunk is less than the max chunk size.
|
|
if (remaining_input_data > chunk_size) {
|
|
if (cipher_mode == kCipherModeCtr) {
|
|
// For 'cenc', update the IV depending on how many encrypted blocks
|
|
// we passed.
|
|
wvutil::AdvanceIvCtr(&fake_sample.iv,
|
|
chunk_size + fake_subsample.block_offset);
|
|
} else if (cipher_mode == kCipherModeCbc) {
|
|
// For 'cbcs', use the last ciphertext block as the next IV. The last
|
|
// block that was encrypted is probably not the last block of the
|
|
// subsample. Since the max buffer size is guaranteed to be an even
|
|
// number of pattern repetitions long, we can use the pattern to know
|
|
// how many blocks to look back.
|
|
const uint8_t* const buffer_end =
|
|
fake_sample.buffers.input_data + chunk_size;
|
|
const uint8_t* const block_end =
|
|
buffer_end - kAes128BlockSize * pattern.skip;
|
|
|
|
static_assert(sizeof(fake_sample.iv) == kAes128BlockSize,
|
|
"The size of an AES-128 block and the size of an AES-128 "
|
|
"IV have become misaligned");
|
|
memcpy(fake_sample.iv, block_end - kAes128BlockSize, kAes128BlockSize);
|
|
}
|
|
}
|
|
|
|
// Update the source and destination buffers based on the amount of data
|
|
// copied.
|
|
fake_sample.buffers.input_data += chunk_size;
|
|
remaining_input_data -= chunk_size;
|
|
AdvanceDestBuffer(&fake_sample.buffers.output_descriptor, chunk_size);
|
|
}
|
|
return sts;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::SetDebugIgnoreKeyboxCount(uint32_t count) {
|
|
OEMCryptoResult status = OEMCrypto_SetDebugIgnoreKeyboxCount(count);
|
|
return MapOEMCryptoResult(status, UNKNOWN_ERROR, "SetDebugIgnoreKeyboxCount");
|
|
}
|
|
|
|
okp::SystemFallbackPolicy* CryptoSession::GetOkpFallbackPolicy() {
|
|
const auto getter = [&]() -> okp::SystemFallbackPolicy* {
|
|
// If not set, then OTA keybox provisioning is not supported or
|
|
// not needed.
|
|
if (!okp_fallback_policy_l1_) return nullptr;
|
|
return okp_fallback_policy_l1_.get();
|
|
};
|
|
return WithStaticFieldReadLock("GetOkpFallbackPolicy", getter);
|
|
}
|
|
|
|
CdmResponseType CryptoSession::PrepareOtaProvisioningRequest(
|
|
bool use_test_key, std::string* request) {
|
|
RETURN_IF_NULL(request, PARAMETER_NULL);
|
|
RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN);
|
|
size_t buffer_length = 0;
|
|
OEMCryptoResult status =
|
|
WithOecWriteLock("PrepareOtaProvisioningRequest", [&] {
|
|
return OEMCrypto_GenerateOTARequest(
|
|
oec_session_id_, nullptr, &buffer_length, use_test_key ? 1 : 0);
|
|
});
|
|
if (status != OEMCrypto_ERROR_SHORT_BUFFER)
|
|
return MapOEMCryptoResult(status, UNKNOWN_ERROR,
|
|
"PrepareOtaProvisioningRequest");
|
|
if (buffer_length == 0) {
|
|
LOGE("OTA request size is zero");
|
|
return CdmResponseType(UNKNOWN_ERROR);
|
|
}
|
|
request->resize(buffer_length);
|
|
uint8_t* buf = reinterpret_cast<uint8_t*>(&request->front());
|
|
status = WithOecWriteLock("PrepareOtaProvisioningRequest", [&] {
|
|
return OEMCrypto_GenerateOTARequest(oec_session_id_, buf, &buffer_length,
|
|
use_test_key ? 1 : 0);
|
|
});
|
|
if (OEMCrypto_SUCCESS != status) {
|
|
request->clear();
|
|
} else if (buffer_length != request->size()) {
|
|
request->resize(buffer_length);
|
|
}
|
|
return MapOEMCryptoResult(status, UNKNOWN_ERROR,
|
|
"PrepareOtaProvisioningRequest");
|
|
}
|
|
|
|
CdmResponseType CryptoSession::LoadOtaProvisioning(
|
|
bool use_test_key, const std::string& response) {
|
|
RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN);
|
|
const OEMCryptoResult status = WithOecWriteLock("LoadOtaProvisioning", [&] {
|
|
return OEMCrypto_ProcessOTAKeybox(
|
|
oec_session_id_, reinterpret_cast<const uint8_t*>(response.data()),
|
|
response.size(), use_test_key ? 1 : 0);
|
|
});
|
|
if (status == OEMCrypto_SUCCESS) {
|
|
WithOecWriteLock("LoadOtaProvisioning",
|
|
[&] { needs_keybox_provisioning_ = false; });
|
|
}
|
|
return MapOEMCryptoResult(status, UNKNOWN_ERROR, "LoadOtaProvisioning");
|
|
}
|
|
|
|
template <class Func>
|
|
auto CryptoSession::WithStaticFieldWriteLock(const char* tag,
|
|
Func body) -> decltype(body()) {
|
|
LOGV("Static field write lock: %s", tag);
|
|
std::unique_lock<wvutil::shared_mutex> auto_lock(static_field_mutex_);
|
|
return body();
|
|
}
|
|
|
|
template <class Func>
|
|
auto CryptoSession::WithStaticFieldReadLock(const char* tag,
|
|
Func body) -> decltype(body()) {
|
|
LOGV("Static field read lock: %s", tag);
|
|
wvutil::shared_lock<wvutil::shared_mutex> auto_lock(static_field_mutex_);
|
|
return body();
|
|
}
|
|
|
|
template <class Func>
|
|
auto CryptoSession::WithOecWriteLock(const char* tag,
|
|
Func body) -> decltype(body()) {
|
|
LOGV("OEMCrypto write lock: %s", tag);
|
|
std::unique_lock<wvutil::shared_mutex> auto_lock(oem_crypto_mutex_);
|
|
return body();
|
|
}
|
|
|
|
template <class Func>
|
|
auto CryptoSession::WithOecReadLock(const char* tag,
|
|
Func body) -> decltype(body()) {
|
|
LOGV("OEMCrypto read lock: %s", tag);
|
|
wvutil::shared_lock<wvutil::shared_mutex> auto_lock(oem_crypto_mutex_);
|
|
return body();
|
|
}
|
|
|
|
template <class Func>
|
|
auto CryptoSession::WithOecSessionLock(const char* tag,
|
|
Func body) -> decltype(body()) {
|
|
LOGV("OEMCrypto session lock: %s", tag);
|
|
wvutil::shared_lock<wvutil::shared_mutex> oec_auto_lock(oem_crypto_mutex_);
|
|
std::unique_lock<std::mutex> session_auto_lock(oem_crypto_session_mutex_);
|
|
return body();
|
|
}
|
|
|
|
bool CryptoSession::IsInitialized() {
|
|
return WithStaticFieldReadLock("IsInitialized", [] { return initialized_; });
|
|
}
|
|
|
|
// CryptoSesssionFactory support
|
|
std::mutex CryptoSession::factory_mutex_;
|
|
// The factory will either be set by WvCdmTestBase, or a default factory is
|
|
// created on the first call to MakeCryptoSession.
|
|
std::unique_ptr<CryptoSessionFactory> CryptoSession::factory_ =
|
|
std::unique_ptr<CryptoSessionFactory>();
|
|
|
|
CryptoSession* CryptoSession::MakeCryptoSession(
|
|
metrics::CryptoMetrics* crypto_metrics) {
|
|
std::unique_lock<std::mutex> auto_lock(factory_mutex_);
|
|
// If the factory_ has not been set, then use a default factory.
|
|
if (!factory_) factory_.reset(new CryptoSessionFactory());
|
|
return factory_->MakeCryptoSession(crypto_metrics);
|
|
}
|
|
|
|
CryptoSession* CryptoSessionFactory::MakeCryptoSession(
|
|
metrics::CryptoMetrics* crypto_metrics) {
|
|
return new CryptoSession(crypto_metrics);
|
|
}
|
|
|
|
} // namespace wvcdm
|