[ Merge of http://go/wvgerrit/168482 ]
The function OEMCrypto_GetBootCertificateChain() does not always
provide an additional signature depending on the device. However, the
CDM would still attempt to dereference the first character in the
additional signature buffer when empty. This CL changes how the data
pointer to an output string is acquired. Empty string will instead
pass in a null pointer.
Bug: 272643393
Test: run_prov40_tests
Test: atest GtsMediaTestCases
Change-Id: I10b0a3c7df4fc73272aa701bb01c60672645d4fc
(cherry picked from commit a878e7b98d)
3332 lines
125 KiB
C++
3332 lines
125 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 (!open_) { \
|
|
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;
|
|
|
|
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;
|
|
}
|
|
|
|
void GenerateMacContext(const std::string& input_context,
|
|
std::string* deriv_context) {
|
|
if (!deriv_context) {
|
|
LOGE("Output parameter |deriv_context| not provided");
|
|
return;
|
|
}
|
|
|
|
const std::string kSigningKeyLabel = "AUTHENTICATION";
|
|
const size_t kSigningKeySizeBits = wvcdm::MAC_KEY_SIZE * 8;
|
|
|
|
deriv_context->assign(kSigningKeyLabel);
|
|
deriv_context->append(1, '\0');
|
|
deriv_context->append(input_context);
|
|
deriv_context->append(wvutil::EncodeUint32(kSigningKeySizeBits * 2));
|
|
}
|
|
|
|
void GenerateEncryptContext(const std::string& input_context,
|
|
std::string* deriv_context) {
|
|
if (!deriv_context) {
|
|
LOGE("Output parameter |deriv_context| not provided");
|
|
return;
|
|
}
|
|
|
|
const std::string kEncryptionKeyLabel = "ENCRYPTION";
|
|
const size_t kEncryptionKeySizeBits = wvcdm::CONTENT_KEY_SIZE * 8;
|
|
|
|
deriv_context->assign(kEncryptionKeyLabel);
|
|
deriv_context->append(1, '\0');
|
|
deriv_context->append(input_context);
|
|
deriv_context->append(wvutil::EncodeUint32(kEncryptionKeySizeBits));
|
|
}
|
|
|
|
OEMCryptoCipherMode ToOEMCryptoCipherMode(CdmCipherMode cipher_mode) {
|
|
return cipher_mode == kCipherModeCtr ? OEMCrypto_CipherMode_CENC
|
|
: OEMCrypto_CipherMode_CBCS;
|
|
}
|
|
|
|
CryptoSession::CryptoSession(metrics::CryptoMetrics* metrics)
|
|
: metrics_(metrics),
|
|
system_id_(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_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);
|
|
}
|
|
metrics_->crypto_session_get_token_.Increment(status);
|
|
return status;
|
|
}
|
|
|
|
CdmSecurityLevel CryptoSession::GetSecurityLevel() {
|
|
LOGV("Getting security level");
|
|
RETURN_IF_NOT_OPEN(kSecurityLevelUninitialized);
|
|
return GetSecurityLevel(requested_security_level_);
|
|
}
|
|
|
|
CdmSecurityLevel CryptoSession::GetSecurityLevel(
|
|
RequestedSecurityLevel requested_security_level) {
|
|
LOGV("Getting security level: requested_security_level = %s",
|
|
RequestedSecurityLevelToString(requested_security_level));
|
|
RETURN_IF_UNINITIALIZED(kSecurityLevelUninitialized);
|
|
const OEMCrypto_Security_Level level = WithOecReadLock(
|
|
"GetSecurityLevel",
|
|
[&] { return OEMCrypto_SecurityLevel(requested_security_level); });
|
|
if (level == 0) {
|
|
LOGE("Security level is unknown: requested_security_level = %d",
|
|
static_cast<int>(requested_security_level));
|
|
return kSecurityLevelUnknown;
|
|
}
|
|
if (level == OEMCrypto_Level1) {
|
|
return kSecurityLevelL1;
|
|
}
|
|
if (level == OEMCrypto_Level2) {
|
|
return kSecurityLevelL2;
|
|
}
|
|
if (level == OEMCrypto_Level3) {
|
|
return kSecurityLevelL3;
|
|
}
|
|
LOGE(
|
|
"Ill-formed security level: "
|
|
"level = \"L%u\", requested_security_level = %s",
|
|
static_cast<unsigned int>(level),
|
|
RequestedSecurityLevelToString(requested_security_level));
|
|
return kSecurityLevelUnknown;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetInternalDeviceUniqueId(
|
|
std::string* device_id) {
|
|
RETURN_IF_NULL(device_id, PARAMETER_NULL);
|
|
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
|
|
|
|
size_t device_id_length = 64;
|
|
device_id->assign(device_id_length, '\0');
|
|
|
|
OEMCryptoResult sts =
|
|
WithOecReadLock("GetInternalDeviceUniqueId Attempt 1", [&] {
|
|
return OEMCrypto_GetDeviceID(
|
|
reinterpret_cast<uint8_t*>(&device_id->front()), &device_id_length,
|
|
requested_security_level_);
|
|
});
|
|
metrics_->oemcrypto_get_device_id_.Increment(sts);
|
|
|
|
if (sts == OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
device_id->resize(device_id_length, '\0');
|
|
sts = WithOecReadLock("GetInternalDeviceUniqueId Attempt 2", [&] {
|
|
return OEMCrypto_GetDeviceID(
|
|
reinterpret_cast<uint8_t*>(&device_id->front()), &device_id_length,
|
|
requested_security_level_);
|
|
});
|
|
metrics_->oemcrypto_get_device_id_.Increment(sts);
|
|
}
|
|
|
|
// Either the authentication root is a keybox or the device has transitioned
|
|
// to using OEMCerts.
|
|
// OEMCryptos, like the Level 3, that transition from Provisioning 2.0 to
|
|
// 3.0 would have a new device ID, which would affect SPOID calculation.
|
|
// In order to resolve this, we use OEMCrypto_GetDeviceID if it is
|
|
// implemented, so the OEMCrypto can continue to report the same device ID.
|
|
if (sts == OEMCrypto_SUCCESS) {
|
|
device_id->resize(device_id_length);
|
|
return 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");
|
|
}
|
|
|
|
CdmResponseType CryptoSession::UseSecondaryKey(bool dual_key) {
|
|
#ifdef HAS_DUAL_KEY
|
|
OEMCryptoResult sts;
|
|
WithOecSessionLock("UseSecondaryKey", [&] {
|
|
sts = OEMCrypto_UseSecondaryKey(oec_session_id_, dual_key);
|
|
});
|
|
|
|
return MapOEMCryptoResult(sts, LOAD_KEY_ERROR, "UseSecondaryKey");
|
|
#else
|
|
return CdmResponseType(NO_ERROR);
|
|
#endif
|
|
}
|
|
|
|
CdmResponseType CryptoSession::LoadLicense(const std::string& signed_message,
|
|
const std::string& core_message,
|
|
const std::string& signature,
|
|
CdmLicenseKeyType key_type) {
|
|
LOGV("Loading license: id = %u", oec_session_id_);
|
|
const std::string combined_message = core_message + signed_message;
|
|
OEMCryptoResult sts;
|
|
WithOecSessionLock("LoadLicense", [&] {
|
|
if (key_type == kLicenseKeyTypeEntitlement &&
|
|
key_session_->Type() != KeySession::kEntitlement) {
|
|
key_session_.reset(new EntitlementKeySession(requested_security_level_,
|
|
oec_session_id_, metrics_));
|
|
}
|
|
|
|
M_TIME(sts = OEMCrypto_LoadLicense(
|
|
oec_session_id_,
|
|
reinterpret_cast<const uint8_t*>(combined_message.data()),
|
|
combined_message.size(), core_message.size(),
|
|
reinterpret_cast<const uint8_t*>(signature.data()),
|
|
signature.size()),
|
|
metrics_, oemcrypto_load_license_, sts);
|
|
});
|
|
|
|
switch (sts) {
|
|
case OEMCrypto_SUCCESS:
|
|
return 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;
|
|
if (pre_provision_token_type_ == kClientTokenKeybox) {
|
|
should_specify_algorithm = false;
|
|
const CdmResponseType status = GenerateDerivedKeys(message);
|
|
if (status != NO_ERROR) return status;
|
|
} 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 {
|
|
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::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);
|
|
});
|
|
|
|
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::GenerateDerivedKeys(const std::string& message) {
|
|
OEMCryptoResult sts;
|
|
WithOecSessionLock("GenerateDerivedKeys without session_key",
|
|
[&] { sts = key_session_->GenerateDerivedKeys(message); });
|
|
|
|
return MapOEMCryptoResult(sts, GENERATE_DERIVED_KEYS_ERROR_2,
|
|
"GenerateDerivedKeys");
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GenerateDerivedKeys(
|
|
const std::string& message, const std::string& session_key) {
|
|
OEMCryptoResult sts;
|
|
WithOecSessionLock("GenerateDerivedKeys with session_key", [&] {
|
|
sts = key_session_->GenerateDerivedKeys(message, session_key);
|
|
});
|
|
|
|
return MapOEMCryptoResult(sts, GENERATE_DERIVED_KEYS_ERROR,
|
|
"GenerateDerivedKeys");
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GenerateRsaSignature(const std::string& message,
|
|
std::string* signature) {
|
|
LOGV("Generating RSA signature: id = %u", oec_session_id_);
|
|
RETURN_IF_NULL(signature, PARAMETER_NULL);
|
|
|
|
OEMCryptoResult sts;
|
|
signature->resize(kRsaSignatureLength);
|
|
size_t length = signature->size();
|
|
|
|
// At most two attempts.
|
|
// The first attempt may fail due to buffer too short
|
|
for (int i = 0; i < 2; ++i) {
|
|
WithOecSessionLock("GenerateRsaSignature", [&] {
|
|
M_TIME(
|
|
sts = OEMCrypto_GenerateRSASignature(
|
|
oec_session_id_, reinterpret_cast<const uint8_t*>(message.data()),
|
|
message.size(),
|
|
reinterpret_cast<uint8_t*>(const_cast<char*>(signature->data())),
|
|
&length, kSign_RSASSA_PSS),
|
|
metrics_, oemcrypto_generate_rsa_signature_, sts,
|
|
metrics::Pow2Bucket(length));
|
|
});
|
|
|
|
if (OEMCrypto_SUCCESS == sts) {
|
|
// Trim signature buffer and done
|
|
signature->resize(length);
|
|
return 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);
|
|
|
|
if (sts != OEMCrypto_SUCCESS && last_decrypt_error_ != sts) {
|
|
// Decrypt errors and warnings are only logged when the error code
|
|
// changes. This is in anticipation that if an error code is
|
|
// returned, then the same error code is likely to be returned in
|
|
// the next call. The calling application may make several more
|
|
// decrypt requests before the error is handled by the app.
|
|
last_decrypt_error_ = sts;
|
|
if (sts == OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION) {
|
|
LOGW(
|
|
"OEMCrypto_DecryptCENC is warning of mixed HDCP output protection: "
|
|
"oec_session_id = %u",
|
|
oec_session_id_);
|
|
} else {
|
|
LOGE(
|
|
"OEMCrypto_DecryptCENC failed: 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& 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*>(combined_message.data()),
|
|
combined_message.size(), core_message.size(),
|
|
reinterpret_cast<const uint8_t*>(signature.data()),
|
|
signature.size(), nullptr, &wrapped_private_key_length),
|
|
metrics_, oemcrypto_load_provisioning_, status);
|
|
});
|
|
|
|
if (status != OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
return MapOEMCryptoResult(status, LOAD_PROVISIONING_ERROR,
|
|
"LoadProvisioning");
|
|
}
|
|
|
|
wrapped_private_key->resize(wrapped_private_key_length);
|
|
|
|
WithOecSessionLock("LoadProvisioning Attempt 2", [&] {
|
|
M_TIME(status = OEMCrypto_LoadProvisioning(
|
|
oec_session_id_,
|
|
reinterpret_cast<const uint8_t*>(combined_message.data()),
|
|
combined_message.size(), core_message.size(),
|
|
reinterpret_cast<const uint8_t*>(signature.data()),
|
|
signature.size(),
|
|
reinterpret_cast<uint8_t*>(&wrapped_private_key->front()),
|
|
&wrapped_private_key_length),
|
|
metrics_, oemcrypto_load_provisioning_, status);
|
|
});
|
|
|
|
if (status == OEMCrypto_SUCCESS) {
|
|
wrapped_private_key->resize(wrapped_private_key_length);
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
wrapped_private_key->clear();
|
|
return MapOEMCryptoResult(status, LOAD_PROVISIONING_ERROR,
|
|
"LoadProvisioning");
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetHdcpCapabilities(HdcpCapability* current,
|
|
HdcpCapability* max) {
|
|
LOGV("Getting HDCP capabilities: id = %u", oec_session_id_);
|
|
RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN);
|
|
return GetHdcpCapabilities(requested_security_level_, current, max);
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetHdcpCapabilities(
|
|
RequestedSecurityLevel security_level, HdcpCapability* current,
|
|
HdcpCapability* max) {
|
|
LOGV("Getting HDCP capabilities: id = %u, security_level = %s",
|
|
oec_session_id_, RequestedSecurityLevelToString(security_level));
|
|
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
|
|
RETURN_IF_NULL(current, PARAMETER_NULL);
|
|
RETURN_IF_NULL(max, PARAMETER_NULL);
|
|
|
|
OEMCryptoResult status;
|
|
WithOecReadLock("GetHdcpCapabilities", [&] {
|
|
status = OEMCrypto_GetHDCPCapability(security_level, current, max);
|
|
});
|
|
|
|
if (OEMCrypto_SUCCESS == status) {
|
|
metrics_->oemcrypto_current_hdcp_capability_.Record(*current);
|
|
metrics_->oemcrypto_max_hdcp_capability_.Record(*max);
|
|
} else {
|
|
metrics_->oemcrypto_current_hdcp_capability_.SetError(status);
|
|
metrics_->oemcrypto_max_hdcp_capability_.SetError(status);
|
|
}
|
|
|
|
return MapOEMCryptoResult(status, GET_HDCP_CAPABILITY_FAILED,
|
|
"GetHDCPCapability");
|
|
}
|
|
|
|
bool CryptoSession::GetSupportedCertificateTypes(
|
|
SupportedCertificateTypes* support) {
|
|
LOGV("Getting supported certificate types: id = %u", oec_session_id_);
|
|
RETURN_IF_UNINITIALIZED(false);
|
|
RETURN_IF_NULL(support, false);
|
|
const uint32_t oec_support =
|
|
WithOecReadLock("GetSupportedCertificateTypes", [&] {
|
|
return OEMCrypto_SupportedCertificates(requested_security_level_);
|
|
});
|
|
support->rsa_2048_bit = oec_support & OEMCrypto_Supports_RSA_2048bit;
|
|
support->rsa_3072_bit = oec_support & OEMCrypto_Supports_RSA_3072bit;
|
|
support->rsa_cast = oec_support & OEMCrypto_Supports_RSA_CAST;
|
|
support->ecc_secp256r1 = oec_support & OEMCrypto_Supports_ECC_secp256r1;
|
|
support->ecc_secp384r1 = oec_support & OEMCrypto_Supports_ECC_secp384r1;
|
|
support->ecc_secp521r1 = oec_support & OEMCrypto_Supports_ECC_secp521r1;
|
|
return true;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::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;
|
|
WithOecSessionLock("SetDecryptHash", [&] {
|
|
sts = OEMCrypto_SetDecryptHash(
|
|
oec_session_id_, frame_number,
|
|
reinterpret_cast<const uint8_t*>(hash.data()), hash.size());
|
|
metrics_->oemcrypto_set_decrypt_hash_.Increment(sts);
|
|
});
|
|
|
|
return MapOEMCryptoResult(sts, SET_DECRYPT_HASH_ERROR, "SetDecryptHash");
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetDecryptHashError(std::string* error_string) {
|
|
LOGV("Getting decrypt hash error");
|
|
RETURN_IF_NULL(error_string, PARAMETER_NULL);
|
|
error_string->clear();
|
|
|
|
uint32_t failed_frame_number = 0;
|
|
OEMCryptoResult sts;
|
|
WithOecSessionLock("GetDecryptHashError", [&] {
|
|
sts = OEMCrypto_GetHashErrorCode(oec_session_id_, &failed_frame_number);
|
|
});
|
|
error_string->assign(std::to_string(sts));
|
|
|
|
if (sts != OEMCrypto_SUCCESS) {
|
|
LOGE("OEMCrypto_GetHashErrorCode failed: status = %d",
|
|
static_cast<int>(sts));
|
|
}
|
|
|
|
switch (sts) {
|
|
case OEMCrypto_SUCCESS:
|
|
case OEMCrypto_ERROR_BAD_HASH:
|
|
error_string->assign(std::to_string(sts));
|
|
error_string->append(",");
|
|
error_string->append(std::to_string(failed_frame_number));
|
|
return 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) {
|
|
OEMCryptoResult sts = OEMCrypto_ERROR_BUFFER_TOO_LARGE;
|
|
|
|
// If there's only one sample, automatically fall through to avoid a redundant
|
|
// roundtrip through OEMCrypto_DecryptCENC()
|
|
if (samples.size() > 1) {
|
|
WithOecSessionLock("DecryptMultipleSamples", [&] {
|
|
sts = key_session_->Decrypt(samples.data(), samples.size(), pattern);
|
|
});
|
|
}
|
|
|
|
if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE) {
|
|
// Fall back to sending each sample individually
|
|
for (const OEMCrypto_SampleDescription& sample : samples) {
|
|
sts = DecryptSample(sample, cipher_mode, pattern);
|
|
if (sts != OEMCrypto_SUCCESS) break;
|
|
}
|
|
}
|
|
|
|
return sts;
|
|
}
|
|
|
|
OEMCryptoResult CryptoSession::DecryptSample(
|
|
const OEMCrypto_SampleDescription& sample, CdmCipherMode cipher_mode,
|
|
const OEMCrypto_CENCEncryptPatternDesc& pattern) {
|
|
OEMCryptoResult sts = OEMCrypto_ERROR_BUFFER_TOO_LARGE;
|
|
|
|
// If there's only one subsample and it contains only one type of region,
|
|
// automatically fall through to avoid a redundant roundtrip through
|
|
// OEMCrypto_DecryptCENC()
|
|
if (sample.subsamples_length > 1 ||
|
|
(sample.subsamples[0].num_bytes_clear > 0 &&
|
|
sample.subsamples[0].num_bytes_encrypted > 0)) {
|
|
WithOecSessionLock("DecryptSample", [&] {
|
|
sts = key_session_->Decrypt(&sample, 1, pattern);
|
|
});
|
|
}
|
|
|
|
if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE) {
|
|
// Fall back to sending each subsample region individually
|
|
sts = OEMCrypto_SUCCESS;
|
|
OEMCrypto_SampleDescription fake_sample = sample;
|
|
for (size_t i = 0; i < sample.subsamples_length; ++i) {
|
|
const OEMCrypto_SubSampleDescription& original_subsample =
|
|
sample.subsamples[i];
|
|
|
|
if (original_subsample.num_bytes_clear > 0) {
|
|
const size_t length = original_subsample.num_bytes_clear;
|
|
OEMCrypto_SubSampleDescription clear_subsample{
|
|
length,
|
|
0, // num_bytes_encrypted
|
|
0, // subsample_flags
|
|
0 // block_offset, not relevant for clear data
|
|
};
|
|
if (original_subsample.subsample_flags & OEMCrypto_FirstSubsample) {
|
|
clear_subsample.subsample_flags |= OEMCrypto_FirstSubsample;
|
|
}
|
|
if ((original_subsample.subsample_flags & OEMCrypto_LastSubsample) &&
|
|
original_subsample.num_bytes_encrypted == 0) {
|
|
clear_subsample.subsample_flags |= OEMCrypto_LastSubsample;
|
|
}
|
|
|
|
fake_sample.buffers.input_data_length = length;
|
|
fake_sample.subsamples = &clear_subsample;
|
|
fake_sample.subsamples_length = 1;
|
|
|
|
sts = LegacyDecrypt(fake_sample, cipher_mode, pattern);
|
|
if (sts != OEMCrypto_SUCCESS) break;
|
|
|
|
fake_sample.buffers.input_data += length;
|
|
AdvanceDestBuffer(&fake_sample.buffers.output_descriptor, length);
|
|
}
|
|
|
|
if (original_subsample.num_bytes_encrypted > 0) {
|
|
const size_t length = original_subsample.num_bytes_encrypted;
|
|
OEMCrypto_SubSampleDescription encrypted_subsample{
|
|
0, // num_bytes_clear
|
|
length,
|
|
0, // subsample_flags
|
|
original_subsample.block_offset};
|
|
if ((original_subsample.subsample_flags & OEMCrypto_FirstSubsample) &&
|
|
original_subsample.num_bytes_clear == 0) {
|
|
encrypted_subsample.subsample_flags |= OEMCrypto_FirstSubsample;
|
|
}
|
|
if (original_subsample.subsample_flags & OEMCrypto_LastSubsample) {
|
|
encrypted_subsample.subsample_flags |= OEMCrypto_LastSubsample;
|
|
}
|
|
|
|
fake_sample.buffers.input_data_length = length;
|
|
fake_sample.subsamples = &encrypted_subsample;
|
|
fake_sample.subsamples_length = 1;
|
|
|
|
sts = LegacyDecrypt(fake_sample, cipher_mode, pattern);
|
|
if (sts != OEMCrypto_SUCCESS) break;
|
|
|
|
fake_sample.buffers.input_data += length;
|
|
AdvanceDestBuffer(&fake_sample.buffers.output_descriptor, length);
|
|
if (cipher_mode == kCipherModeCtr) {
|
|
wvutil::AdvanceIvCtr(&fake_sample.iv,
|
|
original_subsample.block_offset +
|
|
original_subsample.num_bytes_encrypted);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return sts;
|
|
}
|
|
|
|
OEMCryptoResult CryptoSession::LegacyDecrypt(
|
|
const OEMCrypto_SampleDescription& sample, CdmCipherMode cipher_mode,
|
|
const OEMCrypto_CENCEncryptPatternDesc& pattern) {
|
|
const size_t max_chunk_size = GetMaxSubsampleRegionSize();
|
|
OEMCryptoResult sts = OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
|
|
|
// We can be sure this is only called with one subsample containing one
|
|
// region of data.
|
|
const OEMCrypto_SubSampleDescription& subsample = sample.subsamples[0];
|
|
const bool is_encrypted = (subsample.num_bytes_encrypted > 0);
|
|
const bool is_only_subsample =
|
|
subsample.subsample_flags ==
|
|
(OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample);
|
|
if (!is_encrypted && is_only_subsample) {
|
|
WithOecSessionLock("LegacyDecrypt() calling CopyBuffer", [&] {
|
|
M_TIME(sts = OEMCrypto_CopyBuffer(
|
|
oec_session_id_, sample.buffers.input_data,
|
|
sample.buffers.input_data_length,
|
|
&sample.buffers.output_descriptor, subsample.subsample_flags),
|
|
metrics_, oemcrypto_copy_buffer_, sts,
|
|
metrics::Pow2Bucket(sample.buffers.input_data_length));
|
|
});
|
|
|
|
if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE &&
|
|
sample.buffers.input_data_length > max_chunk_size) {
|
|
// OEMCrypto_CopyBuffer rejected the buffer as too large, so chunk it up
|
|
// into 100 KiB sections.
|
|
sts = LegacyCopyBufferInChunks(sample, max_chunk_size);
|
|
}
|
|
}
|
|
if (is_encrypted || sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) {
|
|
WithOecSessionLock("LegacyDecrypt() calling key_session_->Decrypt()", [&] {
|
|
sts = key_session_->Decrypt(&sample, 1, pattern);
|
|
});
|
|
|
|
if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE) {
|
|
// OEMCrypto_DecryptCENC rejected the buffer as too large, so chunk it
|
|
// up into sections no more than 100 KiB. The exact chunk size needs to
|
|
// be an even number of pattern repetitions long or else the pattern
|
|
// will get out of sync.
|
|
const size_t pattern_length =
|
|
(pattern.encrypt + pattern.skip) * kAes128BlockSize;
|
|
const size_t chunk_size =
|
|
pattern_length > 0
|
|
? max_chunk_size - (max_chunk_size % pattern_length)
|
|
: max_chunk_size;
|
|
|
|
if (sample.buffers.input_data_length > chunk_size) {
|
|
sts = LegacyDecryptInChunks(sample, cipher_mode, pattern, chunk_size);
|
|
}
|
|
}
|
|
}
|
|
|
|
return sts;
|
|
}
|
|
|
|
OEMCryptoResult CryptoSession::LegacyCopyBufferInChunks(
|
|
const OEMCrypto_SampleDescription& sample, size_t max_chunk_size) {
|
|
const uint8_t* input_data = sample.buffers.input_data;
|
|
size_t remaining_input_data = sample.buffers.input_data_length;
|
|
OEMCrypto_DestBufferDesc output_descriptor = sample.buffers.output_descriptor;
|
|
uint8_t subsample_flags = OEMCrypto_FirstSubsample;
|
|
OEMCryptoResult sts = OEMCrypto_SUCCESS;
|
|
|
|
while (remaining_input_data > 0) {
|
|
// Calculate the size of the next chunk.
|
|
const size_t chunk_size = std::min(remaining_input_data, max_chunk_size);
|
|
|
|
// Re-add "last subsample" flag if this is the last subsample.
|
|
if (chunk_size == remaining_input_data) {
|
|
subsample_flags |= OEMCrypto_LastSubsample;
|
|
}
|
|
|
|
WithOecSessionLock("LegacyCopyBufferInChunks", [&] {
|
|
M_TIME(sts = OEMCrypto_CopyBuffer(oec_session_id_, input_data, chunk_size,
|
|
&output_descriptor, subsample_flags),
|
|
metrics_, oemcrypto_copy_buffer_, sts,
|
|
metrics::Pow2Bucket(chunk_size));
|
|
});
|
|
|
|
if (sts != OEMCrypto_SUCCESS) break;
|
|
|
|
// Clear any subsample flags before the next loop iteration.
|
|
subsample_flags = 0;
|
|
|
|
// Update the source and destination buffers based on the amount of data
|
|
// copied.
|
|
input_data += chunk_size;
|
|
remaining_input_data -= chunk_size;
|
|
AdvanceDestBuffer(&output_descriptor, chunk_size);
|
|
}
|
|
|
|
return sts;
|
|
}
|
|
|
|
OEMCryptoResult CryptoSession::LegacyDecryptInChunks(
|
|
const OEMCrypto_SampleDescription& sample, CdmCipherMode cipher_mode,
|
|
const OEMCrypto_CENCEncryptPatternDesc& pattern, size_t max_chunk_size) {
|
|
const OEMCrypto_SubSampleDescription& subsample = sample.subsamples[0];
|
|
|
|
const bool is_protected = (subsample.num_bytes_encrypted > 0);
|
|
OEMCrypto_SampleDescription fake_sample = sample;
|
|
OEMCrypto_SubSampleDescription fake_subsample = subsample;
|
|
fake_sample.subsamples = &fake_subsample;
|
|
fake_subsample.subsample_flags =
|
|
subsample.subsample_flags & OEMCrypto_FirstSubsample;
|
|
|
|
size_t remaining_input_data = sample.buffers.input_data_length;
|
|
OEMCryptoResult sts = OEMCrypto_SUCCESS;
|
|
|
|
while (remaining_input_data > 0) {
|
|
// Calculate the size of the next chunk.
|
|
const size_t chunk_size = std::min(remaining_input_data, max_chunk_size);
|
|
fake_sample.buffers.input_data_length = chunk_size;
|
|
if (is_protected) {
|
|
fake_subsample.num_bytes_encrypted = chunk_size;
|
|
} else {
|
|
fake_subsample.num_bytes_clear = chunk_size;
|
|
}
|
|
|
|
// Re-add "last subsample" flag if this is the last subsample.
|
|
if (chunk_size == remaining_input_data) {
|
|
fake_subsample.subsample_flags |=
|
|
subsample.subsample_flags & OEMCrypto_LastSubsample;
|
|
}
|
|
|
|
// |pattern| and |fake_subsample.block_offset| do not need to change because
|
|
// |max_chunk_size| is guaranteed to be an even multiple of the
|
|
// pattern length long, which is also guaranteed to be an exact number
|
|
// of AES blocks long.
|
|
WithOecSessionLock("LegacyDecryptInChunks", [&] {
|
|
sts = key_session_->Decrypt(&fake_sample, 1, pattern);
|
|
});
|
|
|
|
if (sts != OEMCrypto_SUCCESS) break;
|
|
|
|
// Clear any subsample flags before the next loop iteration.
|
|
fake_subsample.subsample_flags = 0;
|
|
|
|
// Update the IV so that it is valid for the next iteration. This should not
|
|
// be done on the last iteration both to save time and because the 'cbcs'
|
|
// calculation can underflow if the chunk is less than the max chunk size.
|
|
if (remaining_input_data > chunk_size) {
|
|
if (cipher_mode == kCipherModeCtr) {
|
|
// For 'cenc', update the IV depending on how many encrypted blocks
|
|
// we passed.
|
|
wvutil::AdvanceIvCtr(&fake_sample.iv,
|
|
chunk_size + fake_subsample.block_offset);
|
|
} else if (cipher_mode == kCipherModeCbc) {
|
|
// For 'cbcs', use the last ciphertext block as the next IV. The last
|
|
// block that was encrypted is probably not the last block of the
|
|
// subsample. Since the max buffer size is guaranteed to be an even
|
|
// number of pattern repetitions long, we can use the pattern to know
|
|
// how many blocks to look back.
|
|
const uint8_t* const buffer_end =
|
|
fake_sample.buffers.input_data + chunk_size;
|
|
const uint8_t* const block_end =
|
|
buffer_end - kAes128BlockSize * pattern.skip;
|
|
|
|
static_assert(sizeof(fake_sample.iv) == kAes128BlockSize,
|
|
"The size of an AES-128 block and the size of an AES-128 "
|
|
"IV have become misaligned");
|
|
memcpy(fake_sample.iv, block_end - kAes128BlockSize, kAes128BlockSize);
|
|
}
|
|
}
|
|
|
|
// Update the source and destination buffers based on the amount of data
|
|
// copied.
|
|
fake_sample.buffers.input_data += chunk_size;
|
|
remaining_input_data -= chunk_size;
|
|
AdvanceDestBuffer(&fake_sample.buffers.output_descriptor, chunk_size);
|
|
}
|
|
return sts;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::SetDebugIgnoreKeyboxCount(uint32_t count) {
|
|
OEMCryptoResult status = OEMCrypto_SetDebugIgnoreKeyboxCount(count);
|
|
return MapOEMCryptoResult(status, UNKNOWN_ERROR, "SetDebugIgnoreKeyboxCount");
|
|
}
|
|
|
|
CdmResponseType CryptoSession::SetAllowTestKeybox(bool allow) {
|
|
OEMCryptoResult status = OEMCrypto_SetAllowTestKeybox(allow);
|
|
return MapOEMCryptoResult(status, UNKNOWN_ERROR, "SetAllowTestKeybox");
|
|
}
|
|
|
|
okp::SystemFallbackPolicy* CryptoSession::GetOkpFallbackPolicy() {
|
|
const auto getter = [&]() -> okp::SystemFallbackPolicy* {
|
|
// If not set, then OTA keybox provisioning is not supported or
|
|
// not needed.
|
|
if (!okp_fallback_policy_l1_) return nullptr;
|
|
return okp_fallback_policy_l1_.get();
|
|
};
|
|
return WithStaticFieldReadLock("GetOkpFallbackPolicy", getter);
|
|
}
|
|
|
|
CdmResponseType CryptoSession::PrepareOtaProvisioningRequest(
|
|
bool use_test_key, std::string* request) {
|
|
RETURN_IF_NULL(request, PARAMETER_NULL);
|
|
RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN);
|
|
size_t buffer_length = 0;
|
|
OEMCryptoResult status =
|
|
WithOecWriteLock("PrepareOtaProvisioningRequest", [&] {
|
|
return OEMCrypto_GenerateOTARequest(
|
|
oec_session_id_, nullptr, &buffer_length, use_test_key ? 1 : 0);
|
|
});
|
|
if (status != OEMCrypto_ERROR_SHORT_BUFFER)
|
|
return MapOEMCryptoResult(status, UNKNOWN_ERROR,
|
|
"PrepareOtaProvisioningRequest");
|
|
if (buffer_length == 0) {
|
|
LOGE("OTA request size is zero");
|
|
return 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
|