[ Merge of http://go/wvgerrit/89906 ] The change allows the GetDecryptHashSupport method to return an error. Bug: 144851430 Test: WV android unit/integration tests Change-Id: Ib3b95788adb21b5ed0daee51ad338f9674b04c3c
2766 lines
98 KiB
C++
2766 lines
98 KiB
C++
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
|
// source code may only be used and distributed under the Widevine Master
|
|
// License Agreement.
|
|
//
|
|
// Crypto - wrapper classes for OEMCrypto interface
|
|
//
|
|
|
|
#include "crypto_session.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include <algorithm>
|
|
#include <iostream>
|
|
#include <memory>
|
|
|
|
#include "content_key_session.h"
|
|
#include "crypto_key.h"
|
|
#include "entitlement_key_session.h"
|
|
#include "log.h"
|
|
#include "platform.h"
|
|
#include "privacy_crypto.h"
|
|
#include "properties.h"
|
|
#include "pst_report.h"
|
|
#include "string_conversions.h"
|
|
#include "usage_table_header.h"
|
|
#include "wv_cdm_constants.h"
|
|
|
|
// Stringify turns macro arguments into static C strings.
|
|
// Example: STRINGIFY(this_argument) -> "this_argument"
|
|
#define STRINGIFY(PARAM...) #PARAM
|
|
|
|
#define RETURN_IF_NULL(PARAM, ret_value) \
|
|
if ((PARAM) == nullptr) { \
|
|
LOGE("Output parameter |" STRINGIFY(PARAM) "| not provided"); \
|
|
return ret_value; \
|
|
}
|
|
|
|
#define RETURN_IF_UNINITIALIZED(ret_value) \
|
|
if (!IsInitialized()) { \
|
|
LOGE("Crypto session is not initialized"); \
|
|
return ret_value; \
|
|
}
|
|
|
|
#define RETURN_IF_NOT_OPEN(ret_value) \
|
|
if (!open_) { \
|
|
LOGE("Crypto session is not open"); \
|
|
return ret_value; \
|
|
}
|
|
|
|
namespace {
|
|
|
|
// Encode unsigned integer into a big endian formatted string
|
|
std::string EncodeUint32(unsigned int u) {
|
|
std::string s;
|
|
s.append(1, (u >> 24) & 0xFF);
|
|
s.append(1, (u >> 16) & 0xFF);
|
|
s.append(1, (u >> 8) & 0xFF);
|
|
s.append(1, (u >> 0) & 0xFF);
|
|
return s;
|
|
}
|
|
|
|
const uint32_t kRsaSignatureLength = 256;
|
|
// TODO(b/117112392): adjust chunk size based on resource rating.
|
|
const size_t kMaximumChunkSize = 100 * 1024; // 100 KiB
|
|
const size_t kEstimatedInitialUsageTableHeader = 40;
|
|
const size_t kOemCryptoApiVersionSupportsBigUsageTables = 13;
|
|
// Ability to switch cipher modes in SelectKey() was introduced in this
|
|
// OEMCrypto version
|
|
const size_t kOemCryptoApiVersionSupportsSwitchingCipherMode = 14;
|
|
|
|
// Constants and utility objects relating to OEM Certificates
|
|
const char* const kWidevineSystemIdExtensionOid = "1.3.6.1.4.1.11129.4.1.1";
|
|
|
|
} // namespace
|
|
|
|
namespace wvcdm {
|
|
shared_mutex CryptoSession::static_field_mutex_;
|
|
shared_mutex CryptoSession::oem_crypto_mutex_;
|
|
bool CryptoSession::initialized_ = false;
|
|
int CryptoSession::session_count_ = 0;
|
|
UsageTableHeader* CryptoSession::usage_table_header_l1_ = nullptr;
|
|
UsageTableHeader* CryptoSession::usage_table_header_l3_ = nullptr;
|
|
std::atomic<uint64_t> CryptoSession::request_id_index_source_(0);
|
|
|
|
size_t GetOffset(std::string message, std::string field) {
|
|
size_t pos = message.find(field);
|
|
if (pos == std::string::npos) {
|
|
LOGE("Cannot find the |field| offset in message: field = %s",
|
|
field.c_str());
|
|
pos = 0;
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
OEMCrypto_Substring GetSubstring(const std::string& message,
|
|
const std::string& field, bool set_zero) {
|
|
OEMCrypto_Substring substring;
|
|
if (set_zero || field.empty() || message.empty()) {
|
|
substring.offset = 0;
|
|
substring.length = 0;
|
|
} else {
|
|
size_t pos = message.find(field);
|
|
if (pos == std::string::npos) {
|
|
LOGW("Cannot find the |field| substring in message: field = %s",
|
|
field.c_str());
|
|
substring.offset = 0;
|
|
substring.length = 0;
|
|
} else {
|
|
substring.offset = pos;
|
|
substring.length = field.length();
|
|
}
|
|
}
|
|
return substring;
|
|
}
|
|
|
|
void GenerateMacContext(const std::string& input_context,
|
|
std::string* deriv_context) {
|
|
if (!deriv_context) {
|
|
LOGE("Output parameter |deriv_context| not provided");
|
|
return;
|
|
}
|
|
|
|
const std::string kSigningKeyLabel = "AUTHENTICATION";
|
|
const size_t kSigningKeySizeBits = wvcdm::MAC_KEY_SIZE * 8;
|
|
|
|
deriv_context->assign(kSigningKeyLabel);
|
|
deriv_context->append(1, '\0');
|
|
deriv_context->append(input_context);
|
|
deriv_context->append(EncodeUint32(kSigningKeySizeBits * 2));
|
|
}
|
|
|
|
void GenerateEncryptContext(const std::string& input_context,
|
|
std::string* deriv_context) {
|
|
if (!deriv_context) {
|
|
LOGE("Output parameter |deriv_context| not provided");
|
|
return;
|
|
}
|
|
|
|
const std::string kEncryptionKeyLabel = "ENCRYPTION";
|
|
const size_t kEncryptionKeySizeBits = wvcdm::CONTENT_KEY_SIZE * 8;
|
|
|
|
deriv_context->assign(kEncryptionKeyLabel);
|
|
deriv_context->append(1, '\0');
|
|
deriv_context->append(input_context);
|
|
deriv_context->append(EncodeUint32(kEncryptionKeySizeBits));
|
|
}
|
|
|
|
OEMCrypto_LicenseType OEMCryptoLicenseType(CdmLicenseKeyType cdm_license_type) {
|
|
return cdm_license_type == kLicenseKeyTypeContent
|
|
? OEMCrypto_ContentLicense
|
|
: OEMCrypto_EntitlementLicense;
|
|
}
|
|
|
|
OEMCryptoCipherMode ToOEMCryptoCipherMode(CdmCipherMode cipher_mode) {
|
|
return cipher_mode == kCipherModeCtr ? OEMCrypto_CipherMode_CTR
|
|
: OEMCrypto_CipherMode_CBC;
|
|
}
|
|
|
|
// This maps a few common OEMCryptoResult to CdmResponseType. Many mappings
|
|
// are not universal but are OEMCrypto method specific. Those will be
|
|
// specified in the CryptoSession method rather than here.
|
|
CdmResponseType MapOEMCryptoResult(OEMCryptoResult result,
|
|
CdmResponseType default_status,
|
|
const char* crypto_session_method) {
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("Mapping OEMCrypto result: crypto_session_method = %s, result = %d",
|
|
crypto_session_method ? crypto_session_method : "N/A",
|
|
static_cast<int>(result));
|
|
}
|
|
|
|
switch (result) {
|
|
case OEMCrypto_SUCCESS:
|
|
return NO_ERROR;
|
|
case OEMCrypto_ERROR_NOT_IMPLEMENTED:
|
|
return NOT_IMPLEMENTED_ERROR;
|
|
case OEMCrypto_ERROR_TOO_MANY_SESSIONS:
|
|
return INSUFFICIENT_CRYPTO_RESOURCES;
|
|
case OEMCrypto_ERROR_SESSION_LOST_STATE:
|
|
return SESSION_LOST_STATE_ERROR;
|
|
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
|
|
return SYSTEM_INVALIDATED_ERROR;
|
|
default:
|
|
return default_status;
|
|
}
|
|
}
|
|
|
|
CryptoSession::CryptoSession(metrics::CryptoMetrics* metrics)
|
|
: metrics_(metrics),
|
|
system_id_(-1),
|
|
open_(false),
|
|
pre_provision_token_type_(kClientTokenUninitialized),
|
|
update_usage_table_after_close_session_(false),
|
|
is_destination_buffer_type_valid_(false),
|
|
requested_security_level_(kLevelDefault),
|
|
is_usage_support_type_valid_(false),
|
|
usage_support_type_(kUnknownUsageSupport),
|
|
usage_table_header_(nullptr),
|
|
cipher_mode_(kCipherModeCtr),
|
|
api_version_(0) {
|
|
assert(metrics);
|
|
Init();
|
|
life_span_.Start();
|
|
}
|
|
|
|
CryptoSession::~CryptoSession() {
|
|
if (open_) {
|
|
Close();
|
|
}
|
|
Terminate();
|
|
M_RECORD(metrics_, crypto_session_life_span_, life_span_.AsMs());
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetProvisioningMethod(
|
|
SecurityLevel requested_security_level, CdmClientTokenType* token_type) {
|
|
OEMCrypto_ProvisioningMethod method;
|
|
WithOecReadLock("GetProvisioningMethod", [&] {
|
|
method = OEMCrypto_GetProvisioningMethod(requested_security_level);
|
|
});
|
|
metrics_->oemcrypto_provisioning_method_.Record(method);
|
|
CdmClientTokenType type;
|
|
switch (method) {
|
|
case OEMCrypto_OEMCertificate:
|
|
type = kClientTokenOemCert;
|
|
break;
|
|
case OEMCrypto_Keybox:
|
|
type = kClientTokenKeybox;
|
|
break;
|
|
case OEMCrypto_DrmCertificate:
|
|
type = kClientTokenDrmCert;
|
|
break;
|
|
case OEMCrypto_ProvisioningError:
|
|
default:
|
|
LOGE("OEMCrypto_GetProvisioningMethod failed: method = %d",
|
|
static_cast<int>(method));
|
|
metrics_->oemcrypto_provisioning_method_.SetError(method);
|
|
return GET_PROVISIONING_METHOD_ERROR;
|
|
}
|
|
*token_type = type;
|
|
return NO_ERROR;
|
|
}
|
|
|
|
void CryptoSession::Init() {
|
|
LOGV("Initializing crypto session");
|
|
WithStaticFieldWriteLock("Init", [&] {
|
|
session_count_ += 1;
|
|
if (!initialized_) {
|
|
std::string sandbox_id;
|
|
OEMCryptoResult sts;
|
|
WithOecWriteLock("Init", [&] {
|
|
if (Properties::GetSandboxId(&sandbox_id) && !sandbox_id.empty()) {
|
|
sts = OEMCrypto_SetSandbox(
|
|
reinterpret_cast<const uint8_t*>(sandbox_id.c_str()),
|
|
sandbox_id.length());
|
|
metrics_->oemcrypto_set_sandbox_.Record(sandbox_id);
|
|
}
|
|
M_TIME(sts = OEMCrypto_Initialize(), metrics_, oemcrypto_initialize_,
|
|
sts);
|
|
});
|
|
if (OEMCrypto_SUCCESS != sts) {
|
|
LOGE("OEMCrypto_Initialize failed: status = %d", static_cast<int>(sts));
|
|
return;
|
|
}
|
|
initialized_ = true;
|
|
}
|
|
});
|
|
}
|
|
|
|
void CryptoSession::Terminate() {
|
|
LOGV("Terminating crypto session");
|
|
WithStaticFieldWriteLock("Terminate", [&] {
|
|
LOGV("Terminating crypto session: initialized_ = %s, session_count_ = %d",
|
|
initialized_ ? "true" : "false", session_count_);
|
|
if (session_count_ > 0) {
|
|
session_count_ -= 1;
|
|
} else {
|
|
LOGE("Invalid crypto session count: session_count_ = %d", session_count_);
|
|
}
|
|
if (session_count_ > 0 || !initialized_) return;
|
|
OEMCryptoResult sts;
|
|
WithOecWriteLock("Terminate", [&] { sts = OEMCrypto_Terminate(); });
|
|
if (OEMCrypto_SUCCESS != sts) {
|
|
LOGE("OEMCrypto_Terminate failed: status = %d", static_cast<int>(sts));
|
|
}
|
|
|
|
if (usage_table_header_l1_ != nullptr) {
|
|
delete usage_table_header_l1_;
|
|
usage_table_header_l1_ = nullptr;
|
|
}
|
|
if (usage_table_header_l3_ != nullptr) {
|
|
delete usage_table_header_l3_;
|
|
usage_table_header_l3_ = nullptr;
|
|
}
|
|
|
|
initialized_ = false;
|
|
});
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetTokenFromKeybox(std::string* token) {
|
|
RETURN_IF_NULL(token, PARAMETER_NULL);
|
|
|
|
std::string temp_buffer(KEYBOX_KEY_DATA_SIZE, '\0');
|
|
size_t buf_size = temp_buffer.size();
|
|
uint8_t* buf = reinterpret_cast<uint8_t*>(&temp_buffer[0]);
|
|
|
|
OEMCryptoResult status;
|
|
WithOecReadLock("GetTokenFromKeybox", [&] {
|
|
M_TIME(status =
|
|
OEMCrypto_GetKeyData(buf, &buf_size, requested_security_level_),
|
|
metrics_, oemcrypto_get_key_data_, status,
|
|
metrics::Pow2Bucket(buf_size));
|
|
});
|
|
|
|
if (OEMCrypto_SUCCESS == status) {
|
|
token->swap(temp_buffer);
|
|
}
|
|
|
|
return MapOEMCryptoResult(status, GET_TOKEN_FROM_KEYBOX_ERROR,
|
|
"GetTokenFromKeybox");
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetTokenFromOemCert(std::string* token) {
|
|
RETURN_IF_NULL(token, PARAMETER_NULL);
|
|
|
|
OEMCryptoResult status;
|
|
if (!oem_token_.empty()) {
|
|
token->assign(oem_token_);
|
|
return NO_ERROR;
|
|
}
|
|
std::string temp_buffer(CERTIFICATE_DATA_SIZE, '\0');
|
|
bool retrying = false;
|
|
while (true) {
|
|
size_t buf_size = temp_buffer.size();
|
|
uint8_t* buf = reinterpret_cast<uint8_t*>(&temp_buffer[0]);
|
|
WithOecSessionLock("GetTokenFromOemCert", [&] {
|
|
status =
|
|
OEMCrypto_GetOEMPublicCertificate(oec_session_id_, buf, &buf_size);
|
|
});
|
|
metrics_->oemcrypto_get_oem_public_certificate_.Increment(status);
|
|
|
|
if (OEMCrypto_SUCCESS == status) {
|
|
temp_buffer.resize(buf_size);
|
|
oem_token_.assign(temp_buffer);
|
|
token->assign(temp_buffer);
|
|
return NO_ERROR;
|
|
}
|
|
|
|
if (status == OEMCrypto_ERROR_SHORT_BUFFER && !retrying) {
|
|
temp_buffer.resize(buf_size);
|
|
retrying = true;
|
|
continue;
|
|
}
|
|
|
|
return MapOEMCryptoResult(status, GET_TOKEN_FROM_OEM_CERT_ERROR,
|
|
"GetTokenFromOemCert");
|
|
}
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetProvisioningToken(std::string* token) {
|
|
if (token == nullptr) {
|
|
metrics_->crypto_session_get_token_.Increment(PARAMETER_NULL);
|
|
RETURN_IF_NULL(token, PARAMETER_NULL);
|
|
}
|
|
|
|
if (!IsInitialized()) {
|
|
metrics_->crypto_session_get_token_.Increment(
|
|
CRYPTO_SESSION_NOT_INITIALIZED);
|
|
return CRYPTO_SESSION_NOT_INITIALIZED;
|
|
}
|
|
|
|
CdmResponseType status = UNKNOWN_CLIENT_TOKEN_TYPE;
|
|
if (pre_provision_token_type_ == kClientTokenKeybox) {
|
|
status = GetTokenFromKeybox(token);
|
|
} else if (pre_provision_token_type_ == kClientTokenOemCert) {
|
|
status = GetTokenFromOemCert(token);
|
|
}
|
|
metrics_->crypto_session_get_token_.Increment(status);
|
|
return status;
|
|
}
|
|
|
|
CdmSecurityLevel CryptoSession::GetSecurityLevel() {
|
|
LOGV("Getting security level");
|
|
RETURN_IF_NOT_OPEN(kSecurityLevelUninitialized);
|
|
return GetSecurityLevel(requested_security_level_);
|
|
}
|
|
|
|
CdmSecurityLevel CryptoSession::GetSecurityLevel(
|
|
SecurityLevel requested_level) {
|
|
LOGV("Getting security level: requested_level = %d",
|
|
static_cast<int>(requested_level));
|
|
RETURN_IF_UNINITIALIZED(kSecurityLevelUninitialized);
|
|
|
|
std::string security_level;
|
|
WithOecReadLock("GetSecurityLevel", [&] {
|
|
security_level = OEMCrypto_SecurityLevel(requested_level);
|
|
});
|
|
|
|
if ((security_level.size() != 2) || (security_level.at(0) != 'L')) {
|
|
return kSecurityLevelUnknown;
|
|
}
|
|
|
|
CdmSecurityLevel cdm_security_level;
|
|
switch (security_level.at(1)) {
|
|
case '1':
|
|
cdm_security_level = kSecurityLevelL1;
|
|
break;
|
|
case '2':
|
|
cdm_security_level = kSecurityLevelL2;
|
|
break;
|
|
case '3':
|
|
cdm_security_level = kSecurityLevelL3;
|
|
break;
|
|
default:
|
|
cdm_security_level = kSecurityLevelUnknown;
|
|
break;
|
|
}
|
|
|
|
return cdm_security_level;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetInternalDeviceUniqueId(
|
|
std::string* device_id) {
|
|
RETURN_IF_NULL(device_id, PARAMETER_NULL);
|
|
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
|
|
|
|
std::vector<uint8_t> id;
|
|
size_t id_length = 32;
|
|
id.resize(id_length);
|
|
|
|
OEMCryptoResult sts;
|
|
WithOecReadLock("GetInternalDeviceUniqueId Attempt 1", [&] {
|
|
sts = OEMCrypto_GetDeviceID(&id[0], &id_length, requested_security_level_);
|
|
});
|
|
// Increment the count of times this method was called.
|
|
metrics_->oemcrypto_get_device_id_.Increment(sts);
|
|
if (sts == OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
id.resize(id_length);
|
|
WithOecReadLock("GetInternalDeviceUniqueId Attempt 2", [&] {
|
|
sts =
|
|
OEMCrypto_GetDeviceID(&id[0], &id_length, requested_security_level_);
|
|
});
|
|
metrics_->oemcrypto_get_device_id_.Increment(sts);
|
|
}
|
|
|
|
if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED &&
|
|
pre_provision_token_type_ == kClientTokenOemCert) {
|
|
return GetTokenFromOemCert(device_id);
|
|
} else {
|
|
// Either the authentication root is a keybox or the device has transitioned
|
|
// to using OEMCerts.
|
|
// OEMCryptos, like the Level 3, that transition from Provisioning 2.0 to
|
|
// 3.0 would have a new device ID, which would affect SPOID calculation.
|
|
// In order to resolve this, we use OEMCrypto_GetDeviceID if it is
|
|
// implemented, so the OEMCrypto can continue to report the same device ID.
|
|
if (sts == OEMCrypto_SUCCESS) {
|
|
device_id->assign(reinterpret_cast<char*>(&id[0]), id_length);
|
|
}
|
|
|
|
return MapOEMCryptoResult(sts, GET_DEVICE_ID_ERROR,
|
|
"GetInternalDeviceUniqueId");
|
|
}
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetExternalDeviceUniqueId(
|
|
std::string* device_id) {
|
|
RETURN_IF_NULL(device_id, PARAMETER_NULL);
|
|
|
|
std::string temp;
|
|
CdmResponseType status = GetInternalDeviceUniqueId(&temp);
|
|
|
|
if (status != NO_ERROR) return status;
|
|
|
|
size_t id_length = 0;
|
|
OEMCryptoResult sts;
|
|
WithOecReadLock("GetExternalDeviceUniqueId", [&] {
|
|
sts = OEMCrypto_GetDeviceID(nullptr, &id_length, requested_security_level_);
|
|
});
|
|
metrics_->oemcrypto_get_device_id_.Increment(sts);
|
|
|
|
if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED &&
|
|
pre_provision_token_type_ == kClientTokenOemCert) {
|
|
// To keep the size of the value passed back to the application down, hash
|
|
// the large OEM Public Cert to a smaller value.
|
|
temp = Sha256Hash(temp);
|
|
}
|
|
|
|
*device_id = temp;
|
|
return NO_ERROR;
|
|
}
|
|
|
|
bool CryptoSession::GetApiVersion(uint32_t* version) {
|
|
LOGV("Getting API version");
|
|
RETURN_IF_NOT_OPEN(false);
|
|
return GetApiVersion(requested_security_level_, version);
|
|
}
|
|
|
|
bool CryptoSession::GetApiVersion(SecurityLevel security_level,
|
|
uint32_t* version) {
|
|
LOGV("Getting API version: security_level = %d",
|
|
static_cast<int>(security_level));
|
|
if (!version) {
|
|
LOGE("Output parameter |version| not provided");
|
|
return false;
|
|
}
|
|
RETURN_IF_UNINITIALIZED(false);
|
|
|
|
WithOecReadLock("GetApiVersion",
|
|
[&] { *version = OEMCrypto_APIVersion(security_level); });
|
|
// Record the version into the metrics.
|
|
metrics_->oemcrypto_api_version_.Record(*version);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CryptoSession::GetSystemId(uint32_t* system_id) {
|
|
RETURN_IF_NULL(system_id, false);
|
|
RETURN_IF_UNINITIALIZED(false);
|
|
RETURN_IF_NOT_OPEN(false);
|
|
*system_id = system_id_;
|
|
return true;
|
|
}
|
|
|
|
// This method gets the system id from the keybox key data.
|
|
// This method assumes that OEMCrypto has been initialized before making this
|
|
// call.
|
|
CdmResponseType CryptoSession::GetSystemIdInternal(uint32_t* system_id) {
|
|
RETURN_IF_NULL(system_id, PARAMETER_NULL);
|
|
|
|
if (pre_provision_token_type_ == kClientTokenKeybox) {
|
|
std::string token;
|
|
CdmResponseType status = GetTokenFromKeybox(&token);
|
|
|
|
if (status != NO_ERROR) return status;
|
|
|
|
if (token.size() < 2 * sizeof(uint32_t)) {
|
|
LOGE("Keybox token size too small: token_size = %zu", token.size());
|
|
return KEYBOX_TOKEN_TOO_SHORT;
|
|
}
|
|
|
|
// Decode 32-bit int encoded as network-byte-order byte array starting at
|
|
// index 4.
|
|
uint32_t* id = reinterpret_cast<uint32_t*>(&token[4]);
|
|
*system_id = ntohl(*id);
|
|
return NO_ERROR;
|
|
|
|
} else if (pre_provision_token_type_ == kClientTokenOemCert) {
|
|
// Get the OEM Cert
|
|
std::string oem_cert;
|
|
CdmResponseType status = GetTokenFromOemCert(&oem_cert);
|
|
|
|
if (status != NO_ERROR) return status;
|
|
|
|
if (!ExtractSystemIdFromOemCert(oem_cert, system_id))
|
|
return EXTRACT_SYSTEM_ID_FROM_OEM_CERT_ERROR;
|
|
|
|
return NO_ERROR;
|
|
|
|
// TODO(blueeyes): Support loading the system id from a pre-provisioned
|
|
// Drm certificate.
|
|
} else if (pre_provision_token_type_ == kClientTokenDrmCert) {
|
|
return NO_ERROR;
|
|
} else {
|
|
LOGE("Unsupported pre-provision token type: %d",
|
|
static_cast<int>(pre_provision_token_type_));
|
|
return UNKNOWN_CLIENT_TOKEN_TYPE;
|
|
}
|
|
}
|
|
|
|
bool CryptoSession::ExtractSystemIdFromOemCert(const std::string& oem_cert,
|
|
uint32_t* system_id) {
|
|
return ExtractExtensionValueFromCertificate(
|
|
oem_cert, kWidevineSystemIdExtensionOid, /* cert_index */ 1, system_id);
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetProvisioningId(std::string* provisioning_id) {
|
|
RETURN_IF_NULL(provisioning_id, PARAMETER_NULL);
|
|
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
|
|
|
|
if (pre_provision_token_type_ == kClientTokenOemCert) {
|
|
// OEM Cert devices have no provisioning-unique ID embedded in them, so we
|
|
// synthesize one by using the External Device-Unique ID and inverting all
|
|
// the bits.
|
|
CdmResponseType status = GetExternalDeviceUniqueId(provisioning_id);
|
|
|
|
if (status != NO_ERROR) return status;
|
|
|
|
for (size_t i = 0; i < provisioning_id->size(); ++i) {
|
|
char value = (*provisioning_id)[i];
|
|
(*provisioning_id)[i] = ~value;
|
|
}
|
|
|
|
return NO_ERROR;
|
|
} else if (pre_provision_token_type_ == kClientTokenKeybox) {
|
|
std::string token;
|
|
CdmResponseType status = GetTokenFromKeybox(&token);
|
|
|
|
if (status != NO_ERROR) return status;
|
|
|
|
if (token.size() < 24) {
|
|
LOGE("Keybox token size too small: %zu", token.size());
|
|
return KEYBOX_TOKEN_TOO_SHORT;
|
|
}
|
|
|
|
provisioning_id->assign(reinterpret_cast<char*>(&token[8]), 16);
|
|
return NO_ERROR;
|
|
} else {
|
|
LOGE("Unsupported pre-provision token type: %d",
|
|
static_cast<int>(pre_provision_token_type_));
|
|
return UNKNOWN_CLIENT_TOKEN_TYPE;
|
|
}
|
|
}
|
|
|
|
uint8_t CryptoSession::GetSecurityPatchLevel() {
|
|
uint8_t patch;
|
|
WithOecReadLock("GetSecurityPatchLevel", [&] {
|
|
patch = OEMCrypto_Security_Patch_Level(requested_security_level_);
|
|
});
|
|
metrics_->oemcrypto_security_patch_level_.Record(patch);
|
|
return patch;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::Open(SecurityLevel requested_security_level) {
|
|
LOGD(
|
|
"Opening crypto session: requested_security_level: "
|
|
"requested_security_level = %s",
|
|
requested_security_level == kLevel3
|
|
? QUERY_VALUE_SECURITY_LEVEL_L3.c_str()
|
|
: QUERY_VALUE_SECURITY_LEVEL_DEFAULT.c_str());
|
|
RETURN_IF_UNINITIALIZED(UNKNOWN_ERROR);
|
|
if (open_) return NO_ERROR;
|
|
|
|
CdmResponseType result = GetProvisioningMethod(requested_security_level,
|
|
&pre_provision_token_type_);
|
|
if (result != NO_ERROR) return result;
|
|
|
|
OEMCrypto_SESSION sid;
|
|
requested_security_level_ = requested_security_level;
|
|
OEMCryptoResult sts;
|
|
WithOecWriteLock("Open() calling OEMCrypto_OpenSession", [&] {
|
|
sts = OEMCrypto_OpenSession(&sid, requested_security_level);
|
|
});
|
|
|
|
if (sts != OEMCrypto_SUCCESS) {
|
|
WithStaticFieldReadLock(
|
|
"Open() reporting OEMCrypto_OpenSession Failure", [&] {
|
|
LOGE(
|
|
"OEMCrypto_Open failed: status = %d, session_count_ = %d,"
|
|
" initialized_ = %s",
|
|
static_cast<int>(sts), session_count_,
|
|
initialized_ ? "true" : "false");
|
|
});
|
|
return MapOEMCryptoResult(sts, OPEN_CRYPTO_SESSION_ERROR, "Open");
|
|
}
|
|
|
|
oec_session_id_ = static_cast<CryptoSessionId>(sid);
|
|
LOGV("Opened session: id = %u", oec_session_id_);
|
|
open_ = true;
|
|
|
|
// Get System ID and save it.
|
|
if (GetSystemIdInternal(&system_id_) == NO_ERROR) {
|
|
metrics_->crypto_session_system_id_.Record(system_id_);
|
|
} else {
|
|
LOGE("Failed to fetch system ID");
|
|
metrics_->crypto_session_system_id_.SetError(LOAD_SYSTEM_ID_ERROR);
|
|
return LOAD_SYSTEM_ID_ERROR;
|
|
}
|
|
|
|
// Set up request ID
|
|
uint64_t request_id_base;
|
|
OEMCryptoResult random_sts;
|
|
WithOecReadLock("Open() calling OEMCrypto_GetRandom", [&] {
|
|
random_sts = OEMCrypto_GetRandom(
|
|
reinterpret_cast<uint8_t*>(&request_id_base), sizeof(request_id_base));
|
|
});
|
|
metrics_->oemcrypto_get_random_.Increment(random_sts);
|
|
uint64_t request_id_index =
|
|
request_id_index_source_.fetch_add(1, std::memory_order_relaxed);
|
|
request_id_ = HexEncode(reinterpret_cast<uint8_t*>(&request_id_base),
|
|
sizeof(request_id_base)) +
|
|
HexEncode(reinterpret_cast<uint8_t*>(&request_id_index),
|
|
sizeof(request_id_index));
|
|
|
|
// Initialize key session
|
|
WithOecSessionLock("Open() calling key_session_.reset()", [&] {
|
|
key_session_.reset(new ContentKeySession(oec_session_id_, metrics_));
|
|
});
|
|
|
|
// Set up Usage Table support
|
|
//
|
|
// This MUST be the last thing in the function because it contains the
|
|
// successful return paths of the function. It will attempt to initialize
|
|
// usage tables. However, whether they are employed is determined by
|
|
// information in the license, which will not be received until later. In case
|
|
// usage tables are not even needed, initialization errors here are ignored by
|
|
// returning successfully.
|
|
//
|
|
// TODO(b/141350978): Refactor this code so that it does not have this
|
|
// problem.
|
|
if (!GetApiVersion(&api_version_)) {
|
|
LOGE("Failed to get API version");
|
|
return USAGE_SUPPORT_GET_API_FAILED;
|
|
}
|
|
|
|
CdmUsageSupportType usage_support_type;
|
|
result = GetUsageSupportType(&usage_support_type);
|
|
if (result == NO_ERROR) {
|
|
metrics_->oemcrypto_usage_table_support_.Record(usage_support_type);
|
|
if (usage_support_type == kUsageEntrySupport) {
|
|
CdmSecurityLevel security_level = GetSecurityLevel();
|
|
if (security_level == kSecurityLevelL1 ||
|
|
security_level == kSecurityLevelL3) {
|
|
{
|
|
// This block cannot use |WithStaticFieldWriteLock| because it needs
|
|
// to unlock the lock partway through.
|
|
LOGV("Static field write lock: Open() initializing usage table");
|
|
std::unique_lock<shared_mutex> auto_lock(static_field_mutex_);
|
|
|
|
UsageTableHeader** header = security_level == kSecurityLevelL1
|
|
? &usage_table_header_l1_
|
|
: &usage_table_header_l3_;
|
|
if (*header == nullptr) {
|
|
*header = new UsageTableHeader();
|
|
// Ignore errors since we do not know when a session is opened,
|
|
// if it is intended to be used for offline/usage session related
|
|
// or otherwise.
|
|
auto_lock.unlock();
|
|
bool is_usage_table_header_inited =
|
|
(*header)->Init(security_level, this);
|
|
auto_lock.lock();
|
|
if (!is_usage_table_header_inited) {
|
|
delete *header;
|
|
*header = nullptr;
|
|
usage_table_header_ = nullptr;
|
|
return NO_ERROR;
|
|
}
|
|
}
|
|
usage_table_header_ = *header;
|
|
metrics_->usage_table_header_initial_size_.Record((*header)->size());
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
metrics_->oemcrypto_usage_table_support_.SetError(result);
|
|
}
|
|
|
|
// Do not add logic after this point as it may never get exercised. In the
|
|
// event of errors initializing the usage tables, the code will return early,
|
|
// never reaching this point. See the comment above the "Set up Usage Table
|
|
// support" section for details.
|
|
//
|
|
// TODO(b/139973602): Refactor this code.
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
void CryptoSession::Close() {
|
|
LOGV("Closing crypto session: id = %u, open = %s", oec_session_id_,
|
|
open_ ? "true" : "false");
|
|
if (!open_) return;
|
|
|
|
OEMCryptoResult close_sts;
|
|
bool update_usage_table = false;
|
|
WithOecWriteLock(
|
|
"Close", [&] { close_sts = OEMCrypto_CloseSession(oec_session_id_); });
|
|
metrics_->oemcrypto_close_session_.Increment(close_sts);
|
|
if (OEMCrypto_SUCCESS == close_sts) open_ = false;
|
|
update_usage_table = update_usage_table_after_close_session_;
|
|
if (close_sts == OEMCrypto_SUCCESS && update_usage_table &&
|
|
usage_support_type_ == kUsageTableSupport) {
|
|
UpdateUsageInformation();
|
|
}
|
|
}
|
|
|
|
CdmResponseType CryptoSession::PrepareRequest(const std::string& message,
|
|
bool is_provisioning,
|
|
std::string* signature) {
|
|
RETURN_IF_NULL(signature, PARAMETER_NULL);
|
|
|
|
if (is_provisioning && (pre_provision_token_type_ == kClientTokenKeybox)) {
|
|
CdmResponseType status = GenerateDerivedKeys(message);
|
|
|
|
if (status != NO_ERROR) return status;
|
|
|
|
return GenerateSignature(message, signature);
|
|
} else {
|
|
return GenerateRsaSignature(message, signature);
|
|
}
|
|
}
|
|
|
|
CdmResponseType CryptoSession::PrepareRenewalRequest(const std::string& message,
|
|
std::string* signature) {
|
|
RETURN_IF_NULL(signature, PARAMETER_NULL);
|
|
|
|
return GenerateSignature(message, signature);
|
|
}
|
|
|
|
CdmResponseType CryptoSession::LoadKeys(
|
|
const std::string& message, const std::string& signature,
|
|
const std::string& mac_key_iv, const std::string& mac_key,
|
|
const std::vector<CryptoKey>& keys,
|
|
const std::string& provider_session_token,
|
|
const std::string& srm_requirement, CdmLicenseKeyType key_type) {
|
|
CdmResponseType result = KEY_ADDED;
|
|
|
|
OEMCryptoResult sts;
|
|
WithOecSessionLock("LoadKeys", [&] {
|
|
if (key_type == kLicenseKeyTypeEntitlement &&
|
|
key_session_->Type() != KeySession::kEntitlement) {
|
|
key_session_.reset(new EntitlementKeySession(oec_session_id_, metrics_));
|
|
}
|
|
|
|
LOGV("Loading key: id = %u", oec_session_id_);
|
|
sts = key_session_->LoadKeys(message, signature, mac_key_iv, mac_key, keys,
|
|
provider_session_token, &cipher_mode_,
|
|
srm_requirement);
|
|
});
|
|
|
|
if (sts != OEMCrypto_SUCCESS) {
|
|
LOGE("OEMCrypto_LoadKeys failed: status = %d", static_cast<int>(sts));
|
|
}
|
|
|
|
switch (sts) {
|
|
case OEMCrypto_SUCCESS:
|
|
if (!provider_session_token.empty())
|
|
update_usage_table_after_close_session_ = true;
|
|
result = KEY_ADDED;
|
|
break;
|
|
case OEMCrypto_ERROR_TOO_MANY_KEYS:
|
|
result = INSUFFICIENT_CRYPTO_RESOURCES_4;
|
|
break;
|
|
case OEMCrypto_ERROR_USAGE_TABLE_UNRECOVERABLE:
|
|
// Handle vendor specific error
|
|
return NEED_PROVISIONING;
|
|
case OEMCrypto_ERROR_SESSION_LOST_STATE:
|
|
return SESSION_LOST_STATE_ERROR;
|
|
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
|
|
return SYSTEM_INVALIDATED_ERROR;
|
|
default:
|
|
result = LOAD_KEY_ERROR;
|
|
break;
|
|
}
|
|
|
|
if (!provider_session_token.empty() &&
|
|
usage_support_type_ == kUsageTableSupport) {
|
|
UpdateUsageInformation();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::LoadEntitledContentKeys(
|
|
const std::vector<CryptoKey>& key_array) {
|
|
OEMCryptoResult sts;
|
|
WithOecSessionLock("LoadEntitledContentKeys", [&] {
|
|
sts = key_session_->LoadEntitledContentKeys(key_array);
|
|
});
|
|
|
|
switch (sts) {
|
|
case OEMCrypto_SUCCESS:
|
|
return KEY_ADDED;
|
|
case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES:
|
|
return INSUFFICIENT_CRYPTO_RESOURCES_6;
|
|
case OEMCrypto_ERROR_INVALID_CONTEXT:
|
|
return NOT_AN_ENTITLEMENT_SESSION;
|
|
case OEMCrypto_KEY_NOT_ENTITLED:
|
|
return NO_MATCHING_ENTITLEMENT_KEY;
|
|
case OEMCrypto_ERROR_SESSION_LOST_STATE:
|
|
return SESSION_LOST_STATE_ERROR;
|
|
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
|
|
return SYSTEM_INVALIDATED_ERROR;
|
|
default:
|
|
return LOAD_ENTITLED_CONTENT_KEYS_ERROR;
|
|
}
|
|
}
|
|
|
|
CdmResponseType CryptoSession::LoadCertificatePrivateKey(
|
|
std::string& wrapped_key) {
|
|
// Call OEMCrypto_GetOEMPublicCertificate before OEMCrypto_LoadDeviceRSAKey
|
|
// so it caches the OEMCrypto Public Key and then throw away result
|
|
std::string temp_buffer(CERTIFICATE_DATA_SIZE, '\0');
|
|
size_t buf_size = temp_buffer.size();
|
|
uint8_t* buf = reinterpret_cast<uint8_t*>(&temp_buffer[0]);
|
|
OEMCryptoResult sts;
|
|
WithOecSessionLock(
|
|
"LoadCertificatePrivateKey() calling OEMCrypto_GetOEMPublicCertificate",
|
|
[&] {
|
|
sts =
|
|
OEMCrypto_GetOEMPublicCertificate(oec_session_id_, buf, &buf_size);
|
|
});
|
|
metrics_->oemcrypto_get_oem_public_certificate_.Increment(sts);
|
|
|
|
LOGV("Loading device RSA key: id = %u", oec_session_id_);
|
|
WithOecSessionLock(
|
|
"LoadCertificatePrivateKey() calling OEMCrypto_LoadDeviceRSAKey()", [&] {
|
|
M_TIME(sts = OEMCrypto_LoadDeviceRSAKey(
|
|
oec_session_id_,
|
|
reinterpret_cast<const uint8_t*>(wrapped_key.data()),
|
|
wrapped_key.size()),
|
|
metrics_, oemcrypto_load_device_rsa_key_, sts);
|
|
});
|
|
|
|
return MapOEMCryptoResult(sts, LOAD_DEVICE_RSA_KEY_ERROR,
|
|
"LoadCertificatePrivateKey");
|
|
}
|
|
|
|
CdmResponseType CryptoSession::RefreshKeys(const std::string& message,
|
|
const std::string& signature,
|
|
int num_keys,
|
|
const CryptoKey* key_array) {
|
|
const uint8_t* msg = reinterpret_cast<const uint8_t*>(message.data());
|
|
std::vector<OEMCrypto_KeyRefreshObject> load_key_array(num_keys);
|
|
for (int i = 0; i < num_keys; ++i) {
|
|
const CryptoKey* ki = &key_array[i];
|
|
OEMCrypto_KeyRefreshObject* ko = &load_key_array[i];
|
|
ko->key_id = GetSubstring(message, ki->key_id());
|
|
bool has_key_control = ki->HasKeyControl();
|
|
ko->key_control_iv =
|
|
GetSubstring(message, ki->key_control_iv(), !has_key_control);
|
|
ko->key_control =
|
|
GetSubstring(message, ki->key_control(), !has_key_control);
|
|
}
|
|
LOGV("Refreshing keys: id = %u", oec_session_id_);
|
|
OEMCryptoResult refresh_sts;
|
|
WithOecSessionLock("RefreshKeys", [&] {
|
|
M_TIME(refresh_sts = OEMCrypto_RefreshKeys(
|
|
oec_session_id_, msg, message.size(),
|
|
reinterpret_cast<const uint8_t*>(signature.data()),
|
|
signature.size(), num_keys, &load_key_array[0]),
|
|
metrics_, oemcrypto_refresh_keys_, refresh_sts);
|
|
});
|
|
|
|
if (refresh_sts == OEMCrypto_SUCCESS) return KEY_ADDED;
|
|
|
|
return MapOEMCryptoResult(refresh_sts, REFRESH_KEYS_ERROR, "RefreshKeys");
|
|
}
|
|
|
|
CdmResponseType CryptoSession::SelectKey(const std::string& key_id,
|
|
CdmCipherMode cipher_mode) {
|
|
OEMCryptoResult sts;
|
|
WithOecSessionLock(
|
|
"SelectKey", [&] { sts = key_session_->SelectKey(key_id, cipher_mode); });
|
|
|
|
switch (sts) {
|
|
case OEMCrypto_SUCCESS:
|
|
return NO_ERROR;
|
|
case OEMCrypto_ERROR_KEY_EXPIRED:
|
|
return NEED_KEY;
|
|
case OEMCrypto_ERROR_INSUFFICIENT_HDCP:
|
|
return INSUFFICIENT_OUTPUT_PROTECTION;
|
|
case OEMCrypto_ERROR_ANALOG_OUTPUT:
|
|
return ANALOG_OUTPUT_ERROR;
|
|
case OEMCrypto_ERROR_INVALID_SESSION:
|
|
return INVALID_SESSION_1;
|
|
case OEMCrypto_ERROR_NO_DEVICE_KEY:
|
|
return NO_DEVICE_KEY_1;
|
|
case OEMCrypto_ERROR_NO_CONTENT_KEY:
|
|
return NO_CONTENT_KEY_2;
|
|
case OEMCrypto_KEY_NOT_LOADED: // obsolete.
|
|
return NO_CONTENT_KEY_3;
|
|
case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES:
|
|
return INSUFFICIENT_CRYPTO_RESOURCES_2;
|
|
case OEMCrypto_ERROR_UNKNOWN_FAILURE:
|
|
return UNKNOWN_SELECT_KEY_ERROR_1;
|
|
case OEMCrypto_ERROR_SESSION_LOST_STATE:
|
|
return SESSION_LOST_STATE_ERROR;
|
|
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
|
|
return SYSTEM_INVALIDATED_ERROR;
|
|
case OEMCrypto_ERROR_CONTROL_INVALID:
|
|
case OEMCrypto_ERROR_KEYBOX_INVALID:
|
|
default:
|
|
return UNKNOWN_SELECT_KEY_ERROR_2;
|
|
}
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GenerateDerivedKeys(const std::string& message) {
|
|
OEMCryptoResult sts;
|
|
WithOecSessionLock("GenerateDerivedKeys without session_key",
|
|
[&] { sts = key_session_->GenerateDerivedKeys(message); });
|
|
|
|
return MapOEMCryptoResult(sts, GENERATE_DERIVED_KEYS_ERROR_2,
|
|
"GenerateDerivedKeys");
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GenerateDerivedKeys(
|
|
const std::string& message, const std::string& session_key) {
|
|
OEMCryptoResult sts;
|
|
WithOecSessionLock("GenerateDerivedKeys with session_key", [&] {
|
|
sts = key_session_->GenerateDerivedKeys(message, session_key);
|
|
});
|
|
|
|
return MapOEMCryptoResult(sts, GENERATE_DERIVED_KEYS_ERROR,
|
|
"GenerateDerivedKeys");
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GenerateSignature(const std::string& message,
|
|
std::string* signature) {
|
|
LOGV("Generating signature: id = %u", oec_session_id_);
|
|
RETURN_IF_NULL(signature, PARAMETER_NULL);
|
|
|
|
OEMCryptoResult sts;
|
|
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("GenerateSignature", [&] {
|
|
M_TIME(
|
|
sts = OEMCrypto_GenerateSignature(
|
|
oec_session_id_, reinterpret_cast<const uint8_t*>(message.data()),
|
|
message.size(),
|
|
reinterpret_cast<uint8_t*>(const_cast<char*>(signature->data())),
|
|
&length),
|
|
metrics_, oemcrypto_generate_signature_, sts,
|
|
metrics::Pow2Bucket(length));
|
|
});
|
|
if (OEMCrypto_SUCCESS == sts) {
|
|
// Trim signature buffer and done
|
|
signature->resize(length);
|
|
return NO_ERROR;
|
|
}
|
|
if (OEMCrypto_ERROR_SHORT_BUFFER != sts) {
|
|
break;
|
|
}
|
|
|
|
// Retry with proper-sized signature buffer
|
|
signature->resize(length);
|
|
}
|
|
|
|
return MapOEMCryptoResult(sts, GENERATE_SIGNATURE_ERROR, "GenerateSignature");
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GenerateRsaSignature(const std::string& message,
|
|
std::string* signature) {
|
|
LOGV("Generating RSA signature: id = %u", oec_session_id_);
|
|
RETURN_IF_NULL(signature, PARAMETER_NULL);
|
|
|
|
OEMCryptoResult sts;
|
|
signature->resize(kRsaSignatureLength);
|
|
size_t length = signature->size();
|
|
|
|
// At most two attempts.
|
|
// The first attempt may fail due to buffer too short
|
|
for (int i = 0; i < 2; ++i) {
|
|
WithOecSessionLock("GenerateRsaSignature", [&] {
|
|
M_TIME(
|
|
sts = OEMCrypto_GenerateRSASignature(
|
|
oec_session_id_, reinterpret_cast<const uint8_t*>(message.data()),
|
|
message.size(),
|
|
reinterpret_cast<uint8_t*>(const_cast<char*>(signature->data())),
|
|
&length, kSign_RSASSA_PSS),
|
|
metrics_, oemcrypto_generate_rsa_signature_, sts,
|
|
metrics::Pow2Bucket(length));
|
|
});
|
|
|
|
if (OEMCrypto_SUCCESS == sts) {
|
|
// Trim signature buffer and done
|
|
signature->resize(length);
|
|
return NO_ERROR;
|
|
}
|
|
if (OEMCrypto_ERROR_SHORT_BUFFER != sts) {
|
|
break;
|
|
}
|
|
|
|
// Retry with proper-sized signature buffer
|
|
signature->resize(length);
|
|
}
|
|
|
|
return MapOEMCryptoResult(sts, RSA_SIGNATURE_GENERATION_ERROR,
|
|
"OEMCrypto_GenerateRSASignature");
|
|
}
|
|
|
|
CdmResponseType CryptoSession::Decrypt(const CdmDecryptionParameters& params) {
|
|
if (!is_destination_buffer_type_valid_) {
|
|
if (!SetDestinationBufferType()) return UNKNOWN_ERROR;
|
|
}
|
|
|
|
OEMCrypto_DestBufferDesc buffer_descriptor;
|
|
buffer_descriptor.type =
|
|
params.is_secure ? destination_buffer_type_ : OEMCrypto_BufferType_Clear;
|
|
|
|
if (params.is_secure &&
|
|
buffer_descriptor.type == OEMCrypto_BufferType_Clear) {
|
|
return SECURE_BUFFER_REQUIRED;
|
|
}
|
|
|
|
switch (buffer_descriptor.type) {
|
|
case OEMCrypto_BufferType_Clear:
|
|
buffer_descriptor.buffer.clear.address =
|
|
static_cast<uint8_t*>(params.decrypt_buffer) +
|
|
params.decrypt_buffer_offset;
|
|
buffer_descriptor.buffer.clear.max_length =
|
|
params.decrypt_buffer_length - params.decrypt_buffer_offset;
|
|
break;
|
|
case OEMCrypto_BufferType_Secure:
|
|
buffer_descriptor.buffer.secure.handle = params.decrypt_buffer;
|
|
buffer_descriptor.buffer.secure.offset = params.decrypt_buffer_offset;
|
|
buffer_descriptor.buffer.secure.max_length = params.decrypt_buffer_length;
|
|
break;
|
|
case OEMCrypto_BufferType_Direct:
|
|
buffer_descriptor.type = OEMCrypto_BufferType_Direct;
|
|
buffer_descriptor.buffer.direct.is_video = params.is_video;
|
|
break;
|
|
}
|
|
|
|
OEMCryptoResult sts = OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
|
if (!params.is_encrypted &&
|
|
params.subsample_flags ==
|
|
(OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)) {
|
|
WithOecSessionLock("Decrypt() calling CopyBuffer", [&] {
|
|
M_TIME(sts = OEMCrypto_CopyBuffer(
|
|
oec_session_id_, params.encrypt_buffer, params.encrypt_length,
|
|
&buffer_descriptor, params.subsample_flags),
|
|
metrics_, oemcrypto_copy_buffer_, sts,
|
|
metrics::Pow2Bucket(params.encrypt_length));
|
|
});
|
|
|
|
if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE &&
|
|
params.encrypt_length > kMaximumChunkSize) {
|
|
// OEMCrypto_CopyBuffer rejected the buffer as too large, so chunk it up
|
|
// into 100 KiB sections.
|
|
sts = CopyBufferInChunks(params, buffer_descriptor);
|
|
}
|
|
}
|
|
if (api_version_ < kOemCryptoApiVersionSupportsSwitchingCipherMode) {
|
|
if (params.is_encrypted && params.cipher_mode != cipher_mode_) {
|
|
return INCORRECT_CRYPTO_MODE;
|
|
}
|
|
}
|
|
if (params.is_encrypted || sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) {
|
|
OEMCrypto_CENCEncryptPatternDesc pattern_descriptor;
|
|
pattern_descriptor.encrypt = params.pattern_descriptor.encrypt_blocks;
|
|
pattern_descriptor.skip = params.pattern_descriptor.skip_blocks;
|
|
pattern_descriptor.offset = 0; // Deprecated field
|
|
// Check if key needs to be selected
|
|
if (params.is_encrypted) {
|
|
CdmResponseType result = SelectKey(*params.key_id, params.cipher_mode);
|
|
if (result != NO_ERROR) return result;
|
|
}
|
|
|
|
WithOecSessionLock("Decrypt() calling key_session_->Decrypt()", [&] {
|
|
sts =
|
|
key_session_->Decrypt(params, buffer_descriptor, pattern_descriptor);
|
|
});
|
|
|
|
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_descriptor.encrypt + pattern_descriptor.skip) *
|
|
kAes128BlockSize;
|
|
const size_t chunk_size =
|
|
pattern_length > 0
|
|
? kMaximumChunkSize - (kMaximumChunkSize % pattern_length)
|
|
: kMaximumChunkSize;
|
|
|
|
if (params.encrypt_length > chunk_size) {
|
|
sts = DecryptInChunks(params, buffer_descriptor, pattern_descriptor,
|
|
chunk_size);
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (sts) {
|
|
case OEMCrypto_SUCCESS:
|
|
return NO_ERROR;
|
|
case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES:
|
|
return INSUFFICIENT_CRYPTO_RESOURCES_5;
|
|
case OEMCrypto_ERROR_KEY_EXPIRED:
|
|
return NEED_KEY;
|
|
case OEMCrypto_ERROR_INVALID_SESSION:
|
|
return SESSION_NOT_FOUND_17;
|
|
case OEMCrypto_ERROR_DECRYPT_FAILED:
|
|
case OEMCrypto_ERROR_UNKNOWN_FAILURE:
|
|
return DECRYPT_ERROR;
|
|
case OEMCrypto_ERROR_INSUFFICIENT_HDCP:
|
|
return INSUFFICIENT_OUTPUT_PROTECTION;
|
|
case OEMCrypto_ERROR_ANALOG_OUTPUT:
|
|
return ANALOG_OUTPUT_ERROR;
|
|
case OEMCrypto_ERROR_OUTPUT_TOO_LARGE:
|
|
return OUTPUT_TOO_LARGE_ERROR;
|
|
case OEMCrypto_ERROR_SESSION_LOST_STATE:
|
|
return SESSION_LOST_STATE_ERROR;
|
|
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
|
|
return SYSTEM_INVALIDATED_ERROR;
|
|
default:
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
}
|
|
|
|
bool CryptoSession::UsageInformationSupport(bool* has_support) {
|
|
LOGV("Checking if usage information is supported");
|
|
RETURN_IF_NOT_OPEN(false);
|
|
return UsageInformationSupport(requested_security_level_, has_support);
|
|
}
|
|
|
|
bool CryptoSession::UsageInformationSupport(SecurityLevel security_level,
|
|
bool* has_support) {
|
|
LOGV("Checking if usage information is supported: security_level = %d",
|
|
static_cast<int>(security_level));
|
|
RETURN_IF_UNINITIALIZED(false);
|
|
WithOecReadLock("UsageInformationSupport", [&] {
|
|
*has_support = OEMCrypto_SupportsUsageTable(security_level);
|
|
});
|
|
return true;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::UpdateUsageInformation() {
|
|
LOGV("Updating usage information: id = %u", oec_session_id_);
|
|
RETURN_IF_UNINITIALIZED(UNKNOWN_ERROR);
|
|
|
|
if (usage_table_header_ != nullptr) {
|
|
LOGV("Deprecated for OEMCrypto v13+");
|
|
return NO_ERROR;
|
|
}
|
|
|
|
OEMCryptoResult status;
|
|
WithOecWriteLock("UpdateUsageInformation",
|
|
[&] { status = OEMCrypto_UpdateUsageTable(); });
|
|
metrics_->oemcrypto_update_usage_table_.Increment(status);
|
|
if (status != OEMCrypto_SUCCESS) {
|
|
LOGE("OEMCrypto_UpdateUsageTable failed: status = %d",
|
|
static_cast<int>(status));
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::DeactivateUsageInformation(
|
|
const std::string& provider_session_token) {
|
|
LOGV("Deactivating usage information: id = %u", oec_session_id_);
|
|
|
|
uint8_t* pst = reinterpret_cast<uint8_t*>(
|
|
const_cast<char*>(provider_session_token.data()));
|
|
|
|
// TODO(fredgc or rfrias): make sure oec_session_id_ is valid.
|
|
OEMCryptoResult status;
|
|
WithOecWriteLock("DeactivateUsageInformation", [&] {
|
|
status = OEMCrypto_DeactivateUsageEntry(oec_session_id_, pst,
|
|
provider_session_token.length());
|
|
});
|
|
metrics_->oemcrypto_deactivate_usage_entry_.Increment(status);
|
|
|
|
if (status != OEMCrypto_SUCCESS) {
|
|
LOGE("OEMCrypto_DeactivateUsageEntry failed: status = %d",
|
|
static_cast<int>(status));
|
|
}
|
|
|
|
switch (status) {
|
|
case OEMCrypto_SUCCESS:
|
|
return NO_ERROR;
|
|
case OEMCrypto_ERROR_INVALID_CONTEXT:
|
|
return KEY_CANCELED;
|
|
case OEMCrypto_ERROR_SESSION_LOST_STATE:
|
|
return SESSION_LOST_STATE_ERROR;
|
|
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
|
|
return SYSTEM_INVALIDATED_ERROR;
|
|
default:
|
|
return DEACTIVATE_USAGE_ENTRY_ERROR;
|
|
}
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GenerateUsageReport(
|
|
const std::string& provider_session_token, std::string* usage_report,
|
|
UsageDurationStatus* usage_duration_status, int64_t* seconds_since_started,
|
|
int64_t* seconds_since_last_played) {
|
|
LOGV("Generating usage report: id = %u", oec_session_id_);
|
|
|
|
RETURN_IF_NULL(usage_report, PARAMETER_NULL);
|
|
|
|
uint8_t* pst = reinterpret_cast<uint8_t*>(
|
|
const_cast<char*>(provider_session_token.data()));
|
|
|
|
size_t usage_length = 0;
|
|
OEMCryptoResult status;
|
|
WithOecWriteLock("GenerateUsageReport Attempt 1", [&] {
|
|
status = OEMCrypto_ReportUsage(oec_session_id_, pst,
|
|
provider_session_token.length(), nullptr,
|
|
&usage_length);
|
|
});
|
|
metrics_->oemcrypto_report_usage_.Increment(status);
|
|
|
|
if (status != OEMCrypto_SUCCESS && status != OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
return MapOEMCryptoResult(status, GENERATE_USAGE_REPORT_ERROR,
|
|
"GenerateUsageReport");
|
|
}
|
|
|
|
std::vector<uint8_t> buffer(usage_length);
|
|
|
|
WithOecWriteLock("GenerateUsageReport Attempt 2", [&] {
|
|
status = OEMCrypto_ReportUsage(oec_session_id_, pst,
|
|
provider_session_token.length(), &buffer[0],
|
|
&usage_length);
|
|
});
|
|
metrics_->oemcrypto_report_usage_.Increment(status);
|
|
|
|
if (status != OEMCrypto_SUCCESS) {
|
|
return MapOEMCryptoResult(status, GENERATE_USAGE_REPORT_ERROR,
|
|
"OEMCrypto_ReportUsage");
|
|
}
|
|
|
|
if (usage_length != buffer.size()) {
|
|
buffer.resize(usage_length);
|
|
}
|
|
(*usage_report) =
|
|
std::string(reinterpret_cast<const char*>(&buffer[0]), buffer.size());
|
|
|
|
Unpacked_PST_Report pst_report(&buffer[0]);
|
|
*usage_duration_status = kUsageDurationsInvalid;
|
|
if (usage_length < pst_report.report_size()) {
|
|
LOGE(
|
|
"Parsed usage report smaller than expected: "
|
|
"usage_length = %zu, report_size = %zu",
|
|
usage_length, pst_report.report_size());
|
|
return NO_ERROR; // usage report available but no duration information
|
|
}
|
|
|
|
if (kUnused == pst_report.status()) {
|
|
*usage_duration_status = kUsageDurationPlaybackNotBegun;
|
|
return NO_ERROR;
|
|
}
|
|
LOGV("OEMCrypto_PST_Report.status: %d\n",
|
|
static_cast<int>(pst_report.status()));
|
|
LOGV("OEMCrypto_PST_Report.clock_security_level: %d\n",
|
|
static_cast<int>(pst_report.clock_security_level()));
|
|
LOGV("OEMCrypto_PST_Report.pst_length: %d\n",
|
|
static_cast<int>(pst_report.pst_length()));
|
|
LOGV("OEMCrypto_PST_Report.padding: %d\n",
|
|
static_cast<int>(pst_report.padding()));
|
|
LOGV("OEMCrypto_PST_Report.seconds_since_license_received: %ld\n",
|
|
pst_report.seconds_since_license_received());
|
|
LOGV("OEMCrypto_PST_Report.seconds_since_first_decrypt: %ld\n",
|
|
pst_report.seconds_since_first_decrypt());
|
|
LOGV("OEMCrypto_PST_Report.seconds_since_last_decrypt: %ld\n",
|
|
pst_report.seconds_since_last_decrypt());
|
|
LOGV("OEMCrypto_PST_Report: %s\n", b2a_hex(*usage_report).c_str());
|
|
|
|
if (kInactiveUnused == pst_report.status()) {
|
|
*usage_duration_status = kUsageDurationPlaybackNotBegun;
|
|
return NO_ERROR;
|
|
}
|
|
// Before OEMCrypto v13, When usage report state is inactive, we have to
|
|
// deduce whether the license was ever used.
|
|
if (kInactive == pst_report.status() &&
|
|
(0 > pst_report.seconds_since_first_decrypt() ||
|
|
pst_report.seconds_since_license_received() <
|
|
pst_report.seconds_since_first_decrypt())) {
|
|
*usage_duration_status = kUsageDurationPlaybackNotBegun;
|
|
return NO_ERROR;
|
|
}
|
|
|
|
*usage_duration_status = kUsageDurationsValid;
|
|
*seconds_since_started = pst_report.seconds_since_first_decrypt();
|
|
*seconds_since_last_played = pst_report.seconds_since_last_decrypt();
|
|
return NO_ERROR;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::ReleaseUsageInformation(
|
|
const std::string& message, const std::string& signature,
|
|
const std::string& provider_session_token) {
|
|
LOGV("Releasing usage information: id = %u", oec_session_id_);
|
|
if (usage_table_header_ != nullptr) {
|
|
LOGW("Releasing usage information is deprecated for OEMCrypto v13+");
|
|
return NO_ERROR;
|
|
}
|
|
|
|
const uint8_t* msg = reinterpret_cast<const uint8_t*>(message.data());
|
|
const uint8_t* sig = reinterpret_cast<const uint8_t*>(signature.data());
|
|
const uint8_t* pst = msg + GetOffset(message, provider_session_token);
|
|
|
|
OEMCryptoResult status;
|
|
WithOecWriteLock("ReleaseUsageInformation", [&] {
|
|
status = OEMCrypto_DeleteUsageEntry(
|
|
oec_session_id_, pst, provider_session_token.length(), msg,
|
|
message.length(), sig, signature.length());
|
|
});
|
|
metrics_->oemcrypto_delete_usage_entry_.Increment(status);
|
|
|
|
if (OEMCrypto_SUCCESS != status) {
|
|
LOGE("OEMCrypto_DeleteUsageEntry failed: status = %d",
|
|
static_cast<int>(status));
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
|
|
if (usage_support_type_ == kUsageTableSupport) UpdateUsageInformation();
|
|
return NO_ERROR;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::DeleteUsageInformation(
|
|
const std::string& provider_session_token) {
|
|
CdmResponseType response = NO_ERROR;
|
|
LOGV("Deleting usage information: id = %u", oec_session_id_);
|
|
OEMCryptoResult status;
|
|
WithOecWriteLock("DeleteUsageInformation", [&] {
|
|
status = OEMCrypto_ForceDeleteUsageEntry(
|
|
reinterpret_cast<const uint8_t*>(provider_session_token.c_str()),
|
|
provider_session_token.length());
|
|
});
|
|
metrics_->oemcrypto_force_delete_usage_entry_.Increment(status);
|
|
if (OEMCrypto_SUCCESS != status) {
|
|
LOGE("OEMCrypto_ForceDeleteUsageEntry failed: status = %d",
|
|
static_cast<int>(status));
|
|
response = UNKNOWN_ERROR;
|
|
}
|
|
if (usage_support_type_ == kUsageTableSupport) UpdateUsageInformation();
|
|
return response;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::DeleteMultipleUsageInformation(
|
|
const std::vector<std::string>& provider_session_tokens) {
|
|
LOGV("Deleting multiple usage information: id = %u", oec_session_id_);
|
|
CdmResponseType response = NO_ERROR;
|
|
WithOecWriteLock("DeleteMultipleUsageInformation", [&] {
|
|
for (size_t i = 0; i < provider_session_tokens.size(); ++i) {
|
|
OEMCryptoResult status = OEMCrypto_ForceDeleteUsageEntry(
|
|
reinterpret_cast<const uint8_t*>(provider_session_tokens[i].c_str()),
|
|
provider_session_tokens[i].length());
|
|
metrics_->oemcrypto_force_delete_usage_entry_.Increment(status);
|
|
if (OEMCrypto_SUCCESS != status) {
|
|
LOGW("OEMCrypto_ForceDeleteUsageEntry failed: index = %zu, status = %d",
|
|
i, static_cast<int>(status));
|
|
response = UNKNOWN_ERROR;
|
|
}
|
|
}
|
|
});
|
|
if (usage_support_type_ == kUsageTableSupport) UpdateUsageInformation();
|
|
return response;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::DeleteAllUsageReports() {
|
|
LOGV("Deleting all usage reports");
|
|
OEMCryptoResult status;
|
|
WithOecWriteLock("DeleteAllUsageReports",
|
|
[&] { status = OEMCrypto_DeleteOldUsageTable(); });
|
|
metrics_->oemcrypto_delete_usage_table_.Increment(status);
|
|
if (OEMCrypto_SUCCESS != status) {
|
|
LOGE("OEMCrypto_DeleteOldUsageTable failed: status = %d",
|
|
static_cast<int>(status));
|
|
}
|
|
|
|
if (usage_support_type_ == kUsageTableSupport) UpdateUsageInformation();
|
|
return NO_ERROR;
|
|
}
|
|
|
|
bool CryptoSession::IsAntiRollbackHwPresent() {
|
|
bool is_present;
|
|
WithOecReadLock("IsAntiRollbackHwPresent", [&] {
|
|
is_present = OEMCrypto_IsAntiRollbackHwPresent(requested_security_level_);
|
|
});
|
|
metrics_->oemcrypto_is_anti_rollback_hw_present_.Record(is_present);
|
|
return is_present;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GenerateNonce(uint32_t* nonce) {
|
|
RETURN_IF_NULL(nonce, PARAMETER_NULL);
|
|
|
|
OEMCryptoResult result;
|
|
WithOecWriteLock("GenerateNonce", [&] {
|
|
result = OEMCrypto_GenerateNonce(oec_session_id_, nonce);
|
|
});
|
|
metrics_->oemcrypto_generate_nonce_.Increment(result);
|
|
|
|
return MapOEMCryptoResult(result, NONCE_GENERATION_ERROR, "GenerateNonce");
|
|
}
|
|
|
|
bool CryptoSession::SetDestinationBufferType() {
|
|
if (Properties::oem_crypto_use_secure_buffers()) {
|
|
destination_buffer_type_ = OEMCrypto_BufferType_Secure;
|
|
} else if (Properties::oem_crypto_use_fifo()) {
|
|
destination_buffer_type_ = OEMCrypto_BufferType_Direct;
|
|
} else if (Properties::oem_crypto_use_userspace_buffers()) {
|
|
destination_buffer_type_ = OEMCrypto_BufferType_Clear;
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
is_destination_buffer_type_valid_ = true;
|
|
return true;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::RewrapCertificate(
|
|
const std::string& signed_message, const std::string& signature,
|
|
const std::string& nonce, const std::string& private_key,
|
|
const std::string& iv, const std::string& wrapping_key,
|
|
std::string* wrapped_private_key) {
|
|
LOGV("Rewrapping certificate: id = %u", oec_session_id_);
|
|
|
|
if (pre_provision_token_type_ == kClientTokenKeybox) {
|
|
return RewrapDeviceRSAKey(signed_message, signature, nonce, private_key, iv,
|
|
wrapped_private_key);
|
|
|
|
} else if (pre_provision_token_type_ == kClientTokenOemCert) {
|
|
return RewrapDeviceRSAKey30(signed_message, nonce, private_key, iv,
|
|
wrapping_key, wrapped_private_key);
|
|
|
|
} else {
|
|
LOGE("Bad pre-provision type: %d, id = %u",
|
|
static_cast<int>(pre_provision_token_type_), oec_session_id_);
|
|
return UNKNOWN_CLIENT_TOKEN_TYPE;
|
|
}
|
|
}
|
|
|
|
CdmResponseType CryptoSession::RewrapDeviceRSAKey(
|
|
const std::string& message, const std::string& signature,
|
|
const std::string& nonce, const std::string& enc_rsa_key,
|
|
const std::string& rsa_key_iv, std::string* wrapped_rsa_key) {
|
|
LOGV("Rewrapping device RSA key: id = %u", oec_session_id_);
|
|
|
|
const uint8_t* signed_msg = reinterpret_cast<const uint8_t*>(message.data());
|
|
const uint8_t* msg_rsa_key = nullptr;
|
|
const uint8_t* msg_rsa_key_iv = nullptr;
|
|
const uint32_t* msg_nonce = nullptr;
|
|
if (enc_rsa_key.size() >= MAC_KEY_SIZE && rsa_key_iv.size() >= KEY_IV_SIZE) {
|
|
msg_rsa_key = signed_msg + GetOffset(message, enc_rsa_key);
|
|
msg_rsa_key_iv = signed_msg + GetOffset(message, rsa_key_iv);
|
|
msg_nonce = reinterpret_cast<const uint32_t*>(signed_msg +
|
|
GetOffset(message, nonce));
|
|
}
|
|
|
|
// Gets wrapped_rsa_key_length by passing nullptr as uint8_t* wrapped_rsa_key
|
|
// and 0 as wrapped_rsa_key_length.
|
|
size_t wrapped_rsa_key_length = 0;
|
|
OEMCryptoResult status;
|
|
WithOecSessionLock("RewrapDeviceRSAKey Attempt 1", [&] {
|
|
M_TIME(status = OEMCrypto_RewrapDeviceRSAKey(
|
|
oec_session_id_, signed_msg, message.size(),
|
|
reinterpret_cast<const uint8_t*>(signature.data()),
|
|
signature.size(), msg_nonce, msg_rsa_key, enc_rsa_key.size(),
|
|
msg_rsa_key_iv, nullptr, &wrapped_rsa_key_length),
|
|
metrics_, oemcrypto_rewrap_device_rsa_key_, status);
|
|
});
|
|
|
|
if (status != OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
return MapOEMCryptoResult(status, REWRAP_DEVICE_RSA_KEY_ERROR,
|
|
"RewrapDeviceRSAKey");
|
|
}
|
|
|
|
wrapped_rsa_key->resize(wrapped_rsa_key_length);
|
|
WithOecSessionLock("RewrapDeviceRSAKey Attempt 2", [&] {
|
|
M_TIME(
|
|
status = OEMCrypto_RewrapDeviceRSAKey(
|
|
oec_session_id_, signed_msg, message.size(),
|
|
reinterpret_cast<const uint8_t*>(signature.data()),
|
|
signature.size(), msg_nonce, msg_rsa_key, enc_rsa_key.size(),
|
|
msg_rsa_key_iv, reinterpret_cast<uint8_t*>(&(*wrapped_rsa_key)[0]),
|
|
&wrapped_rsa_key_length),
|
|
metrics_, oemcrypto_rewrap_device_rsa_key_, status);
|
|
});
|
|
|
|
wrapped_rsa_key->resize(wrapped_rsa_key_length);
|
|
|
|
return MapOEMCryptoResult(status, REWRAP_DEVICE_RSA_KEY_ERROR,
|
|
"RewrapDeviceRSAKey");
|
|
}
|
|
|
|
CdmResponseType CryptoSession::RewrapDeviceRSAKey30(
|
|
const std::string& message, const std::string& nonce,
|
|
const std::string& private_key, const std::string& iv,
|
|
const std::string& wrapping_key, std::string* wrapped_private_key) {
|
|
LOGV("Rewrapping Device RSA key 30: id = %u", oec_session_id_);
|
|
|
|
const uint8_t* signed_msg = reinterpret_cast<const uint8_t*>(message.data());
|
|
const uint8_t* msg_private_key = nullptr;
|
|
const uint8_t* msg_iv = nullptr;
|
|
const uint32_t* msg_nonce = nullptr;
|
|
const uint8_t* msg_wrapping_key = nullptr;
|
|
if (private_key.size() >= MAC_KEY_SIZE && iv.size() >= KEY_IV_SIZE) {
|
|
msg_private_key = signed_msg + GetOffset(message, private_key);
|
|
msg_iv = signed_msg + GetOffset(message, iv);
|
|
msg_nonce = reinterpret_cast<const uint32_t*>(signed_msg +
|
|
GetOffset(message, nonce));
|
|
msg_wrapping_key = signed_msg + GetOffset(message, wrapping_key);
|
|
}
|
|
|
|
// Gets wrapped_rsa_key_length by passing nullptr as uint8_t* wrapped_rsa_key
|
|
// and 0 as wrapped_rsa_key_length.
|
|
size_t wrapped_private_key_length = 0;
|
|
OEMCryptoResult status;
|
|
WithOecSessionLock("RewrapDeviceRSAKey30 Attempt 1", [&] {
|
|
M_TIME(status = OEMCrypto_RewrapDeviceRSAKey30(
|
|
oec_session_id_, msg_nonce, msg_wrapping_key,
|
|
wrapping_key.size(), msg_private_key, private_key.size(), msg_iv,
|
|
nullptr, &wrapped_private_key_length),
|
|
metrics_, oemcrypto_rewrap_device_rsa_key_30_, status);
|
|
});
|
|
|
|
if (status != OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
return MapOEMCryptoResult(status, REWRAP_DEVICE_RSA_KEY_30_ERROR,
|
|
"RewrapDeviceRSAKey30");
|
|
}
|
|
|
|
wrapped_private_key->resize(wrapped_private_key_length);
|
|
WithOecSessionLock("RewrapDeviceRSAKey30 Attempt 2", [&] {
|
|
M_TIME(status = OEMCrypto_RewrapDeviceRSAKey30(
|
|
oec_session_id_, msg_nonce, msg_wrapping_key,
|
|
wrapping_key.size(), msg_private_key, private_key.size(), msg_iv,
|
|
reinterpret_cast<uint8_t*>(&(*wrapped_private_key)[0]),
|
|
&wrapped_private_key_length),
|
|
metrics_, oemcrypto_rewrap_device_rsa_key_30_, status);
|
|
});
|
|
|
|
wrapped_private_key->resize(wrapped_private_key_length);
|
|
|
|
return MapOEMCryptoResult(status, REWRAP_DEVICE_RSA_KEY_30_ERROR,
|
|
"RewrapDeviceRSAKey30");
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetHdcpCapabilities(HdcpCapability* current,
|
|
HdcpCapability* max) {
|
|
LOGV("Getting HDCP capabilities: id = %u", oec_session_id_);
|
|
RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN);
|
|
return GetHdcpCapabilities(requested_security_level_, current, max);
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetHdcpCapabilities(SecurityLevel security_level,
|
|
HdcpCapability* current,
|
|
HdcpCapability* max) {
|
|
LOGV("Getting HDCP capabilities: id = %u, security_level = %d",
|
|
oec_session_id_, static_cast<int>(security_level));
|
|
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
|
|
RETURN_IF_NULL(current, PARAMETER_NULL);
|
|
RETURN_IF_NULL(max, PARAMETER_NULL);
|
|
|
|
OEMCryptoResult status;
|
|
WithOecReadLock("GetHdcpCapabilities", [&] {
|
|
status = OEMCrypto_GetHDCPCapability(security_level, current, max);
|
|
});
|
|
|
|
if (OEMCrypto_SUCCESS == status) {
|
|
metrics_->oemcrypto_current_hdcp_capability_.Record(*current);
|
|
metrics_->oemcrypto_max_hdcp_capability_.Record(*max);
|
|
} else {
|
|
metrics_->oemcrypto_current_hdcp_capability_.SetError(status);
|
|
metrics_->oemcrypto_max_hdcp_capability_.SetError(status);
|
|
}
|
|
|
|
return MapOEMCryptoResult(status, GET_HDCP_CAPABILITY_FAILED,
|
|
"GetHDCPCapability");
|
|
}
|
|
|
|
bool CryptoSession::GetSupportedCertificateTypes(
|
|
SupportedCertificateTypes* support) {
|
|
LOGV("Getting supported certificate types: id = %u", oec_session_id_);
|
|
RETURN_IF_UNINITIALIZED(false);
|
|
RETURN_IF_NULL(support, false);
|
|
|
|
uint32_t oec_support;
|
|
WithOecReadLock("GetSupportedCertificateTypes", [&] {
|
|
oec_support = OEMCrypto_SupportedCertificates(requested_security_level_);
|
|
});
|
|
support->rsa_2048_bit = oec_support & OEMCrypto_Supports_RSA_2048bit;
|
|
support->rsa_3072_bit = oec_support & OEMCrypto_Supports_RSA_3072bit;
|
|
support->rsa_cast = oec_support & OEMCrypto_Supports_RSA_CAST;
|
|
return true;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetRandom(size_t data_length,
|
|
uint8_t* random_data) {
|
|
RETURN_IF_NULL(random_data, PARAMETER_NULL);
|
|
|
|
OEMCryptoResult sts;
|
|
WithOecReadLock("GetRandom",
|
|
[&] { sts = OEMCrypto_GetRandom(random_data, data_length); });
|
|
metrics_->oemcrypto_get_random_.Increment(sts);
|
|
|
|
return MapOEMCryptoResult(sts, RANDOM_GENERATION_ERROR, "GetRandom");
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetNumberOfOpenSessions(
|
|
SecurityLevel security_level, size_t* count) {
|
|
LOGV("Getting number of open sessions: id = %u, security_level = %d",
|
|
oec_session_id_, static_cast<int>(security_level));
|
|
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
|
|
RETURN_IF_NULL(count, PARAMETER_NULL);
|
|
|
|
size_t sessions_count;
|
|
OEMCryptoResult status;
|
|
WithOecReadLock("GetNumberOfOpenSessions", [&] {
|
|
status = OEMCrypto_GetNumberOfOpenSessions(security_level, &sessions_count);
|
|
});
|
|
|
|
if (OEMCrypto_SUCCESS == status) {
|
|
metrics_->oemcrypto_number_of_open_sessions_.Record(sessions_count);
|
|
*count = sessions_count;
|
|
} else {
|
|
metrics_->oemcrypto_number_of_open_sessions_.SetError(status);
|
|
}
|
|
|
|
return MapOEMCryptoResult(status, GET_NUMBER_OF_OPEN_SESSIONS_ERROR,
|
|
"GetNumberOfOpenSessions");
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetMaxNumberOfSessions(
|
|
SecurityLevel security_level, size_t* max) {
|
|
LOGV("Getting max number of sessions: id = %u, security_level = %d",
|
|
oec_session_id_, static_cast<int>(security_level));
|
|
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
|
|
RETURN_IF_NULL(max, PARAMETER_NULL);
|
|
|
|
size_t max_sessions = 0;
|
|
OEMCryptoResult status;
|
|
WithOecReadLock("GetMaxNumberOfSessions", [&] {
|
|
status = OEMCrypto_GetMaxNumberOfSessions(security_level, &max_sessions);
|
|
});
|
|
|
|
if (OEMCrypto_SUCCESS == status) {
|
|
metrics_->oemcrypto_max_number_of_sessions_.Record(max_sessions);
|
|
*max = max_sessions;
|
|
} else {
|
|
metrics_->oemcrypto_max_number_of_sessions_.SetError(status);
|
|
}
|
|
|
|
return MapOEMCryptoResult(status, GET_MAX_NUMBER_OF_OPEN_SESSIONS_ERROR,
|
|
"GetMaxNumberOfOpenSessions");
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetSrmVersion(uint16_t* srm_version) {
|
|
LOGV("Getting SRM version");
|
|
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
|
|
RETURN_IF_NULL(srm_version, PARAMETER_NULL);
|
|
|
|
OEMCryptoResult status;
|
|
WithOecReadLock("GetSrmVersion", [&] {
|
|
status = OEMCrypto_GetCurrentSRMVersion(srm_version);
|
|
});
|
|
|
|
// SRM is an optional feature. Whether it is implemented is up to the
|
|
// discretion of OEMs
|
|
if (status == OEMCrypto_ERROR_NOT_IMPLEMENTED) {
|
|
LOGV("OEMCrypto_GetCurrentSRMVersion not implemented");
|
|
return NOT_IMPLEMENTED_ERROR;
|
|
}
|
|
|
|
return MapOEMCryptoResult(status, GET_SRM_VERSION_ERROR,
|
|
"GetCurrentSRMVersion");
|
|
}
|
|
|
|
bool CryptoSession::IsSrmUpdateSupported() {
|
|
LOGV("Checking if SRM update is supported");
|
|
if (!IsInitialized()) return false;
|
|
return WithOecReadLock("IsSrmUpdateSupported",
|
|
[&] { return OEMCrypto_IsSRMUpdateSupported(); });
|
|
}
|
|
|
|
CdmResponseType CryptoSession::LoadSrm(const std::string& srm) {
|
|
LOGV("Loading SRM");
|
|
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
|
|
if (srm.empty()) {
|
|
LOGE("SRM is empty");
|
|
return INVALID_SRM_LIST;
|
|
}
|
|
|
|
OEMCryptoResult status;
|
|
WithOecWriteLock("LoadSrm", [&] {
|
|
status = OEMCrypto_LoadSRM(reinterpret_cast<const uint8_t*>(srm.data()),
|
|
srm.size());
|
|
});
|
|
|
|
return MapOEMCryptoResult(status, LOAD_SRM_ERROR, "LoadSRM");
|
|
}
|
|
|
|
bool CryptoSession::GetResourceRatingTier(uint32_t* tier) {
|
|
LOGV("Getting resource rating tier");
|
|
RETURN_IF_NOT_OPEN(false);
|
|
return GetResourceRatingTier(requested_security_level_, tier);
|
|
}
|
|
|
|
bool CryptoSession::GetResourceRatingTier(SecurityLevel security_level,
|
|
uint32_t* tier) {
|
|
LOGV("Getting resource rating tier: security_level = %d",
|
|
static_cast<int>(security_level));
|
|
RETURN_IF_UNINITIALIZED(false);
|
|
RETURN_IF_NULL(tier, false);
|
|
|
|
WithOecReadLock("GetResourceRatingTier", [&] {
|
|
*tier = OEMCrypto_ResourceRatingTier(security_level);
|
|
metrics_->oemcrypto_resource_rating_tier_.Record(*tier);
|
|
});
|
|
if (*tier < RESOURCE_RATING_TIER_LOW || *tier > RESOURCE_RATING_TIER_HIGH) {
|
|
uint32_t api_version;
|
|
if (GetApiVersion(security_level, &api_version)) {
|
|
if (api_version >= OEM_CRYPTO_API_VERSION_SUPPORTS_RESOURCE_RATING_TIER) {
|
|
LOGW("Invalid resource rating tier: %u", *tier);
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool CryptoSession::GetBuildInformation(std::string* info) {
|
|
LOGV("Getting build information");
|
|
RETURN_IF_NOT_OPEN(false);
|
|
return GetBuildInformation(requested_security_level_, info);
|
|
}
|
|
|
|
bool CryptoSession::GetBuildInformation(SecurityLevel security_level,
|
|
std::string* info) {
|
|
LOGV("Getting build information: security_level = %d",
|
|
static_cast<int>(security_level));
|
|
RETURN_IF_UNINITIALIZED(false);
|
|
RETURN_IF_NULL(info, false);
|
|
|
|
const char* build_information;
|
|
WithOecReadLock("GetBuildInformation", [&] {
|
|
build_information = OEMCrypto_BuildInformation(security_level);
|
|
});
|
|
if (build_information == nullptr) {
|
|
LOGE("OEMCrypto_BuildInformation failed: Returned null");
|
|
return false;
|
|
}
|
|
|
|
info->assign(build_information);
|
|
return true;
|
|
}
|
|
|
|
bool CryptoSession::GetDecryptHashSupport(SecurityLevel security_level,
|
|
uint32_t* decrypt_hash_support) {
|
|
LOGV("Checking if decrypt hash is supported");
|
|
RETURN_IF_UNINITIALIZED(false);
|
|
RETURN_IF_NULL(decrypt_hash_support, false);
|
|
|
|
WithOecReadLock("GetDecryptHashSupport", [&] {
|
|
*decrypt_hash_support = OEMCrypto_SupportsDecryptHash(security_level);
|
|
});
|
|
switch (*decrypt_hash_support) {
|
|
case OEMCrypto_Hash_Not_Supported:
|
|
case OEMCrypto_CRC_Clear_Buffer:
|
|
case OEMCrypto_Partner_Defined_Hash:
|
|
break;
|
|
default:
|
|
// Not flagging an error since it is only used in test
|
|
LOGW("OEMCrypto_SupportsDecryptHash unrecognized result = %d",
|
|
static_cast<int>(*decrypt_hash_support));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::SetDecryptHash(uint32_t frame_number,
|
|
const std::string& hash) {
|
|
LOGV("Setting decrypt hash");
|
|
OEMCryptoResult sts;
|
|
WithOecSessionLock("SetDecryptHash", [&] {
|
|
sts = OEMCrypto_SetDecryptHash(
|
|
oec_session_id_, frame_number,
|
|
reinterpret_cast<const uint8_t*>(hash.data()), hash.size());
|
|
metrics_->oemcrypto_set_decrypt_hash_.Increment(sts);
|
|
});
|
|
|
|
return MapOEMCryptoResult(sts, SET_DECRYPT_HASH_ERROR, "SetDecryptHash");
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetDecryptHashError(std::string* error_string) {
|
|
LOGV("Getting decrypt hash error");
|
|
RETURN_IF_NULL(error_string, PARAMETER_NULL);
|
|
error_string->clear();
|
|
|
|
uint32_t failed_frame_number = 0;
|
|
OEMCryptoResult sts;
|
|
WithOecSessionLock("GetDecryptHashError", [&] {
|
|
sts = OEMCrypto_GetHashErrorCode(oec_session_id_, &failed_frame_number);
|
|
});
|
|
error_string->assign(std::to_string(sts));
|
|
|
|
if (sts != OEMCrypto_SUCCESS) {
|
|
LOGE("OEMCrypto_GetHashErrorCode failed: status = %d",
|
|
static_cast<int>(sts));
|
|
}
|
|
|
|
switch (sts) {
|
|
case OEMCrypto_SUCCESS:
|
|
case OEMCrypto_ERROR_BAD_HASH:
|
|
error_string->assign(std::to_string(sts));
|
|
error_string->append(",");
|
|
error_string->append(std::to_string(failed_frame_number));
|
|
return NO_ERROR;
|
|
case OEMCrypto_ERROR_SESSION_LOST_STATE:
|
|
return SESSION_LOST_STATE_ERROR;
|
|
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
|
|
return SYSTEM_INVALIDATED_ERROR;
|
|
case OEMCrypto_ERROR_UNKNOWN_FAILURE:
|
|
case OEMCrypto_ERROR_NOT_IMPLEMENTED:
|
|
default:
|
|
return GET_DECRYPT_HASH_ERROR;
|
|
}
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GenericEncrypt(const std::string& in_buffer,
|
|
const std::string& key_id,
|
|
const std::string& iv,
|
|
CdmEncryptionAlgorithm algorithm,
|
|
std::string* out_buffer) {
|
|
LOGV("Generic encrypt: id = %u", oec_session_id_);
|
|
RETURN_IF_NULL(out_buffer, PARAMETER_NULL);
|
|
|
|
OEMCrypto_Algorithm oec_algorithm = GenericEncryptionAlgorithm(algorithm);
|
|
if (iv.size() != GenericEncryptionBlockSize(algorithm) ||
|
|
oec_algorithm == kInvalidAlgorithm) {
|
|
return INVALID_PARAMETERS_ENG_13;
|
|
}
|
|
|
|
if (out_buffer->size() < in_buffer.size()) {
|
|
out_buffer->resize(in_buffer.size());
|
|
}
|
|
|
|
// TODO(jfore): We need to select a key with a cipher mode and algorithm
|
|
// doesn't seem to fit. Is it ok to just use a default value here?
|
|
// Or do we need to pass it in?
|
|
CdmResponseType result = SelectKey(key_id, kCipherModeCbc);
|
|
if (result != NO_ERROR) return result;
|
|
|
|
OEMCryptoResult sts;
|
|
|
|
WithOecSessionLock("GenericEncrypt", [&] {
|
|
M_TIME(
|
|
sts = OEMCrypto_Generic_Encrypt(
|
|
oec_session_id_, reinterpret_cast<const uint8_t*>(in_buffer.data()),
|
|
in_buffer.size(), reinterpret_cast<const uint8_t*>(iv.data()),
|
|
oec_algorithm,
|
|
reinterpret_cast<uint8_t*>(const_cast<char*>(out_buffer->data()))),
|
|
metrics_, oemcrypto_generic_encrypt_, sts,
|
|
metrics::Pow2Bucket(in_buffer.size()));
|
|
});
|
|
|
|
if (OEMCrypto_SUCCESS != sts) {
|
|
LOGE("OEMCrypto_Generic_Encrypt failed: status = %d",
|
|
static_cast<int>(sts));
|
|
}
|
|
|
|
switch (sts) {
|
|
case OEMCrypto_SUCCESS:
|
|
return NO_ERROR;
|
|
case OEMCrypto_ERROR_KEY_EXPIRED:
|
|
return NEED_KEY;
|
|
case OEMCrypto_ERROR_NO_CONTENT_KEY:
|
|
case OEMCrypto_KEY_NOT_LOADED: // obsolete in v15.
|
|
return KEY_NOT_FOUND_3;
|
|
case OEMCrypto_ERROR_OUTPUT_TOO_LARGE:
|
|
return OUTPUT_TOO_LARGE_ERROR;
|
|
case OEMCrypto_ERROR_SESSION_LOST_STATE:
|
|
return SESSION_LOST_STATE_ERROR;
|
|
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
|
|
return SYSTEM_INVALIDATED_ERROR;
|
|
default:
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GenericDecrypt(const std::string& in_buffer,
|
|
const std::string& key_id,
|
|
const std::string& iv,
|
|
CdmEncryptionAlgorithm algorithm,
|
|
std::string* out_buffer) {
|
|
LOGV("Generic decrypt: id = %u", oec_session_id_);
|
|
RETURN_IF_NULL(out_buffer, PARAMETER_NULL);
|
|
|
|
OEMCrypto_Algorithm oec_algorithm = GenericEncryptionAlgorithm(algorithm);
|
|
if (iv.size() != GenericEncryptionBlockSize(algorithm) ||
|
|
oec_algorithm == kInvalidAlgorithm) {
|
|
return INVALID_PARAMETERS_ENG_14;
|
|
}
|
|
|
|
if (out_buffer->size() < in_buffer.size()) {
|
|
out_buffer->resize(in_buffer.size());
|
|
}
|
|
|
|
// TODO(jfore): We need to select a key with a cipher mode and algorithm
|
|
// doesn't seem to fit. Is it ok to just use a default value here?
|
|
// Or do we need to pass it in?
|
|
CdmResponseType result = SelectKey(key_id, kCipherModeCbc);
|
|
if (result != NO_ERROR) return result;
|
|
|
|
OEMCryptoResult sts;
|
|
|
|
WithOecSessionLock("GenericDecrypt", [&] {
|
|
M_TIME(
|
|
sts = OEMCrypto_Generic_Decrypt(
|
|
oec_session_id_, reinterpret_cast<const uint8_t*>(in_buffer.data()),
|
|
in_buffer.size(), reinterpret_cast<const uint8_t*>(iv.data()),
|
|
oec_algorithm,
|
|
reinterpret_cast<uint8_t*>(const_cast<char*>(out_buffer->data()))),
|
|
metrics_, oemcrypto_generic_decrypt_, sts,
|
|
metrics::Pow2Bucket(in_buffer.size()));
|
|
});
|
|
|
|
if (OEMCrypto_SUCCESS != sts) {
|
|
LOGE("OEMCrypto_Generic_Decrypt failed: status = %d",
|
|
static_cast<int>(sts));
|
|
}
|
|
|
|
switch (sts) {
|
|
case OEMCrypto_SUCCESS:
|
|
return NO_ERROR;
|
|
case OEMCrypto_ERROR_KEY_EXPIRED:
|
|
return NEED_KEY;
|
|
case OEMCrypto_ERROR_NO_CONTENT_KEY:
|
|
case OEMCrypto_KEY_NOT_LOADED: // obsolete in v15.
|
|
return KEY_NOT_FOUND_4;
|
|
case OEMCrypto_ERROR_OUTPUT_TOO_LARGE:
|
|
return OUTPUT_TOO_LARGE_ERROR;
|
|
case OEMCrypto_ERROR_SESSION_LOST_STATE:
|
|
return SESSION_LOST_STATE_ERROR;
|
|
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
|
|
return SYSTEM_INVALIDATED_ERROR;
|
|
default:
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GenericSign(const std::string& message,
|
|
const std::string& key_id,
|
|
CdmSigningAlgorithm algorithm,
|
|
std::string* signature) {
|
|
LOGV("Generic sign: id = %u", oec_session_id_);
|
|
RETURN_IF_NULL(signature, PARAMETER_NULL);
|
|
|
|
OEMCrypto_Algorithm oec_algorithm = GenericSigningAlgorithm(algorithm);
|
|
if (oec_algorithm == kInvalidAlgorithm) {
|
|
return INVALID_PARAMETERS_ENG_15;
|
|
}
|
|
|
|
OEMCryptoResult sts;
|
|
size_t length = signature->size();
|
|
|
|
// TODO(jfore): We need to select a key with a cipher mode and algorithm
|
|
// doesn't seem to fit. Is it ok to just use a default value here?
|
|
// Or do we need to pass it in?
|
|
CdmResponseType result = SelectKey(key_id, kCipherModeCbc);
|
|
if (result != NO_ERROR) return result;
|
|
|
|
// At most two attempts.
|
|
// The first attempt may fail due to buffer too short
|
|
for (int i = 0; i < 2; ++i) {
|
|
WithOecSessionLock("GenericSign", [&] {
|
|
M_TIME(
|
|
sts = OEMCrypto_Generic_Sign(
|
|
oec_session_id_, reinterpret_cast<const uint8_t*>(message.data()),
|
|
message.size(), oec_algorithm,
|
|
reinterpret_cast<uint8_t*>(const_cast<char*>(signature->data())),
|
|
&length),
|
|
metrics_, oemcrypto_generic_sign_, sts,
|
|
metrics::Pow2Bucket(message.size()));
|
|
});
|
|
|
|
if (OEMCrypto_SUCCESS == sts) {
|
|
// Trim signature buffer and done
|
|
signature->resize(length);
|
|
return NO_ERROR;
|
|
}
|
|
if (OEMCrypto_ERROR_SHORT_BUFFER != sts) {
|
|
break;
|
|
}
|
|
|
|
// Retry with proper-sized return buffer
|
|
signature->resize(length);
|
|
}
|
|
|
|
LOGE("OEMCrypto_Generic_Sign failed: status = %d", static_cast<int>(sts));
|
|
|
|
switch (sts) {
|
|
case OEMCrypto_ERROR_KEY_EXPIRED:
|
|
return NEED_KEY;
|
|
case OEMCrypto_ERROR_NO_CONTENT_KEY:
|
|
case OEMCrypto_KEY_NOT_LOADED: // obsolete in v15.
|
|
return KEY_NOT_FOUND_5;
|
|
case OEMCrypto_ERROR_OUTPUT_TOO_LARGE:
|
|
return OUTPUT_TOO_LARGE_ERROR;
|
|
case OEMCrypto_ERROR_SESSION_LOST_STATE:
|
|
return SESSION_LOST_STATE_ERROR;
|
|
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
|
|
return SYSTEM_INVALIDATED_ERROR;
|
|
default:
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GenericVerify(const std::string& message,
|
|
const std::string& key_id,
|
|
CdmSigningAlgorithm algorithm,
|
|
const std::string& signature) {
|
|
LOGV("Generic verify: id = %u", oec_session_id_);
|
|
|
|
OEMCrypto_Algorithm oec_algorithm = GenericSigningAlgorithm(algorithm);
|
|
if (oec_algorithm == kInvalidAlgorithm) {
|
|
return INVALID_PARAMETERS_ENG_16;
|
|
}
|
|
|
|
// TODO(jfore): We need to select a key with a cipher mode and algorithm
|
|
// doesn't seem to fit. Is it ok to just use a default value here?
|
|
// Or do we need to pass it in?
|
|
CdmResponseType result = SelectKey(key_id, kCipherModeCbc);
|
|
if (result != NO_ERROR) return result;
|
|
|
|
OEMCryptoResult sts;
|
|
WithOecSessionLock("GenericVerify", [&] {
|
|
M_TIME(
|
|
sts = OEMCrypto_Generic_Verify(
|
|
oec_session_id_, reinterpret_cast<const uint8_t*>(message.data()),
|
|
message.size(), oec_algorithm,
|
|
reinterpret_cast<const uint8_t*>(signature.data()),
|
|
signature.size()),
|
|
metrics_, oemcrypto_generic_verify_, sts,
|
|
metrics::Pow2Bucket(signature.size()));
|
|
});
|
|
|
|
if (OEMCrypto_SUCCESS != sts) {
|
|
LOGE("OEMCrypto_Generic_Verify failed: status = %d", static_cast<int>(sts));
|
|
}
|
|
|
|
switch (sts) {
|
|
case OEMCrypto_SUCCESS:
|
|
return NO_ERROR;
|
|
case OEMCrypto_ERROR_KEY_EXPIRED:
|
|
return NEED_KEY;
|
|
case OEMCrypto_ERROR_NO_CONTENT_KEY:
|
|
case OEMCrypto_KEY_NOT_LOADED: // obsolete in v15.
|
|
return KEY_NOT_FOUND_6;
|
|
case OEMCrypto_ERROR_OUTPUT_TOO_LARGE:
|
|
return OUTPUT_TOO_LARGE_ERROR;
|
|
case OEMCrypto_ERROR_SESSION_LOST_STATE:
|
|
return SESSION_LOST_STATE_ERROR;
|
|
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
|
|
return SYSTEM_INVALIDATED_ERROR;
|
|
default:
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetUsageSupportType(
|
|
CdmUsageSupportType* usage_support_type) {
|
|
LOGV("Getting usage support type: id = %u", oec_session_id_);
|
|
|
|
RETURN_IF_NULL(usage_support_type, PARAMETER_NULL);
|
|
|
|
if (usage_support_type_ != kUnknownUsageSupport) {
|
|
*usage_support_type = usage_support_type_;
|
|
return NO_ERROR;
|
|
}
|
|
|
|
bool has_support = false;
|
|
if (!UsageInformationSupport(&has_support)) {
|
|
LOGE("UsageInformationSupport failed");
|
|
return USAGE_INFORMATION_SUPPORT_FAILED;
|
|
}
|
|
|
|
if (!has_support) {
|
|
*usage_support_type = usage_support_type_ = kNonSecureUsageSupport;
|
|
return NO_ERROR;
|
|
}
|
|
|
|
*usage_support_type = usage_support_type_ =
|
|
(api_version_ >= kOemCryptoApiVersionSupportsBigUsageTables)
|
|
? kUsageEntrySupport
|
|
: kUsageTableSupport;
|
|
return NO_ERROR;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::CreateUsageTableHeader(
|
|
CdmUsageTableHeader* usage_table_header) {
|
|
LOGV("Creating usage table header: id = %u", oec_session_id_);
|
|
|
|
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);
|
|
});
|
|
}
|
|
|
|
if (result == OEMCrypto_SUCCESS) {
|
|
usage_table_header->resize(usage_table_header_size);
|
|
}
|
|
|
|
return MapOEMCryptoResult(result, CREATE_USAGE_TABLE_ERROR,
|
|
"CreateUsageTableHeader");
|
|
}
|
|
|
|
CdmResponseType CryptoSession::LoadUsageTableHeader(
|
|
const CdmUsageTableHeader& usage_table_header) {
|
|
LOGV("Loading usage table header: id = %u", oec_session_id_);
|
|
|
|
OEMCryptoResult result;
|
|
WithOecWriteLock("LoadUsageTableHeader", [&] {
|
|
result = OEMCrypto_LoadUsageTableHeader(
|
|
requested_security_level_,
|
|
reinterpret_cast<const uint8_t*>(usage_table_header.data()),
|
|
usage_table_header.size());
|
|
metrics_->oemcrypto_load_usage_table_header_.Increment(result);
|
|
});
|
|
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
if (result == OEMCrypto_WARNING_GENERATION_SKEW) {
|
|
LOGW("OEMCrypto_LoadUsageTableHeader warning: generation skew");
|
|
} else {
|
|
LOGE("OEMCrypto_LoadUsageTableHeader failed: status = %d",
|
|
static_cast<int>(result));
|
|
}
|
|
}
|
|
|
|
switch (result) {
|
|
case OEMCrypto_SUCCESS:
|
|
case OEMCrypto_WARNING_GENERATION_SKEW:
|
|
return NO_ERROR;
|
|
case OEMCrypto_ERROR_GENERATION_SKEW:
|
|
return LOAD_USAGE_HEADER_GENERATION_SKEW;
|
|
case OEMCrypto_ERROR_SIGNATURE_FAILURE:
|
|
return LOAD_USAGE_HEADER_SIGNATURE_FAILURE;
|
|
case OEMCrypto_ERROR_BAD_MAGIC:
|
|
return LOAD_USAGE_HEADER_BAD_MAGIC;
|
|
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
|
|
return SYSTEM_INVALIDATED_ERROR;
|
|
case OEMCrypto_ERROR_UNKNOWN_FAILURE:
|
|
default:
|
|
return LOAD_USAGE_HEADER_UNKNOWN_ERROR;
|
|
}
|
|
}
|
|
|
|
CdmResponseType CryptoSession::CreateUsageEntry(uint32_t* entry_number) {
|
|
LOGV("Creating usage entry: id = %u", oec_session_id_);
|
|
|
|
RETURN_IF_NULL(entry_number, PARAMETER_NULL);
|
|
|
|
OEMCryptoResult result;
|
|
WithOecWriteLock("CreateUsageEntry", [&] {
|
|
result = OEMCrypto_CreateNewUsageEntry(oec_session_id_, entry_number);
|
|
metrics_->oemcrypto_create_new_usage_entry_.Increment(result);
|
|
});
|
|
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("OEMCrypto_CreateNewUsageEntry failed: status = %d",
|
|
static_cast<int>(result));
|
|
}
|
|
|
|
switch (result) {
|
|
case OEMCrypto_SUCCESS:
|
|
return NO_ERROR;
|
|
case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES:
|
|
return INSUFFICIENT_CRYPTO_RESOURCES_3;
|
|
case OEMCrypto_ERROR_SESSION_LOST_STATE:
|
|
return SESSION_LOST_STATE_ERROR;
|
|
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
|
|
return SYSTEM_INVALIDATED_ERROR;
|
|
default:
|
|
return CREATE_USAGE_ENTRY_UNKNOWN_ERROR;
|
|
}
|
|
}
|
|
|
|
CdmResponseType CryptoSession::LoadUsageEntry(
|
|
uint32_t entry_number, const CdmUsageEntry& usage_entry) {
|
|
LOGV("Loading usage entry: id = %u", oec_session_id_);
|
|
|
|
OEMCryptoResult result;
|
|
WithOecWriteLock("LoadUsageEntry", [&] {
|
|
result = OEMCrypto_LoadUsageEntry(
|
|
oec_session_id_, entry_number,
|
|
reinterpret_cast<const uint8_t*>(usage_entry.data()),
|
|
usage_entry.size());
|
|
metrics_->oemcrypto_load_usage_entry_.Increment(result);
|
|
});
|
|
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
if (result == OEMCrypto_WARNING_GENERATION_SKEW) {
|
|
LOGW("OEMCrypto_LoadUsageEntry warning: generation skew");
|
|
} else {
|
|
LOGE("OEMCrypto_LoadUsageEntry failed: status = %d",
|
|
static_cast<int>(result));
|
|
}
|
|
}
|
|
|
|
switch (result) {
|
|
case OEMCrypto_SUCCESS:
|
|
case OEMCrypto_WARNING_GENERATION_SKEW:
|
|
return NO_ERROR;
|
|
case OEMCrypto_ERROR_GENERATION_SKEW:
|
|
return LOAD_USAGE_ENTRY_GENERATION_SKEW;
|
|
case OEMCrypto_ERROR_SIGNATURE_FAILURE:
|
|
return LOAD_USAGE_ENTRY_SIGNATURE_FAILURE;
|
|
case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES:
|
|
return INSUFFICIENT_CRYPTO_RESOURCES_3;
|
|
case OEMCrypto_ERROR_SESSION_LOST_STATE:
|
|
return SESSION_LOST_STATE_ERROR;
|
|
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
|
|
return SYSTEM_INVALIDATED_ERROR;
|
|
default:
|
|
return LOAD_USAGE_ENTRY_UNKNOWN_ERROR;
|
|
}
|
|
}
|
|
|
|
CdmResponseType CryptoSession::UpdateUsageEntry(
|
|
CdmUsageTableHeader* usage_table_header, CdmUsageEntry* usage_entry) {
|
|
LOGV("Updating usage entry: id = %u", oec_session_id_);
|
|
|
|
RETURN_IF_NULL(usage_table_header, PARAMETER_NULL);
|
|
RETURN_IF_NULL(usage_entry, PARAMETER_NULL);
|
|
|
|
size_t usage_table_header_len = 0;
|
|
size_t usage_entry_len = 0;
|
|
OEMCryptoResult result;
|
|
WithOecWriteLock("UpdateUsageEntry Attempt 1", [&] {
|
|
result = OEMCrypto_UpdateUsageEntry(oec_session_id_, nullptr,
|
|
&usage_table_header_len, nullptr,
|
|
&usage_entry_len);
|
|
});
|
|
metrics_->oemcrypto_update_usage_entry_.Increment(result);
|
|
|
|
if (result == OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
usage_table_header->resize(usage_table_header_len);
|
|
usage_entry->resize(usage_entry_len);
|
|
|
|
WithOecWriteLock("UpdateUsageEntry Attempt 2", [&] {
|
|
result = OEMCrypto_UpdateUsageEntry(
|
|
oec_session_id_,
|
|
reinterpret_cast<uint8_t*>(
|
|
const_cast<char*>(usage_table_header->data())),
|
|
&usage_table_header_len,
|
|
reinterpret_cast<uint8_t*>(const_cast<char*>(usage_entry->data())),
|
|
&usage_entry_len);
|
|
});
|
|
metrics_->oemcrypto_update_usage_entry_.Increment(result);
|
|
}
|
|
|
|
if (result == OEMCrypto_SUCCESS) {
|
|
usage_table_header->resize(usage_table_header_len);
|
|
usage_entry->resize(usage_entry_len);
|
|
}
|
|
|
|
return MapOEMCryptoResult(result, UPDATE_USAGE_ENTRY_UNKNOWN_ERROR,
|
|
"UpdateUsageEntry");
|
|
}
|
|
|
|
CdmResponseType CryptoSession::ShrinkUsageTableHeader(
|
|
uint32_t new_entry_count, CdmUsageTableHeader* usage_table_header) {
|
|
LOGV("Shrinking usage table header: id = %u", oec_session_id_);
|
|
|
|
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);
|
|
});
|
|
}
|
|
|
|
if (result == OEMCrypto_SUCCESS) {
|
|
usage_table_header->resize(usage_table_header_len);
|
|
}
|
|
|
|
return MapOEMCryptoResult(result, SHRINK_USAGE_TABLER_HEADER_UNKNOWN_ERROR,
|
|
"ShrinkUsageTableHeader");
|
|
}
|
|
|
|
CdmResponseType CryptoSession::MoveUsageEntry(uint32_t new_entry_number) {
|
|
LOGV("Moving usage entry: id = %u", oec_session_id_);
|
|
|
|
OEMCryptoResult result;
|
|
WithOecWriteLock("MoveUsageEntry", [&] {
|
|
result = OEMCrypto_MoveEntry(oec_session_id_, new_entry_number);
|
|
metrics_->oemcrypto_move_entry_.Increment(result);
|
|
});
|
|
|
|
return MapOEMCryptoResult(result, MOVE_USAGE_ENTRY_UNKNOWN_ERROR,
|
|
"MoveUsageEntry");
|
|
}
|
|
|
|
bool CryptoSession::CreateOldUsageEntry(
|
|
uint64_t time_since_license_received, uint64_t time_since_first_decrypt,
|
|
uint64_t time_since_last_decrypt, UsageDurationStatus usage_duration_status,
|
|
const std::string& server_mac_key, const std::string& client_mac_key,
|
|
const std::string& provider_session_token) {
|
|
LOGV("Creating old usage entry");
|
|
|
|
if (server_mac_key.size() < MAC_KEY_SIZE) {
|
|
LOGE("Invalid server MAC key size: size = %zu", server_mac_key.size());
|
|
return false;
|
|
}
|
|
if (client_mac_key.size() < MAC_KEY_SIZE) {
|
|
LOGE("Invalid client MAC key size: size = %zu", client_mac_key.size());
|
|
return false;
|
|
}
|
|
|
|
OEMCrypto_Usage_Entry_Status status = kUnused;
|
|
switch (usage_duration_status) {
|
|
case kUsageDurationsInvalid:
|
|
status = kUnused;
|
|
break;
|
|
case kUsageDurationPlaybackNotBegun:
|
|
status = kInactiveUnused;
|
|
break;
|
|
case kUsageDurationsValid:
|
|
status = kActive;
|
|
break;
|
|
default:
|
|
LOGE("Unrecognized usage duration status: %d",
|
|
static_cast<int>(usage_duration_status));
|
|
return false;
|
|
}
|
|
|
|
OEMCryptoResult result;
|
|
WithOecWriteLock("CreateOldUsageEntry", [&] {
|
|
result = OEMCrypto_CreateOldUsageEntry(
|
|
requested_security_level_, time_since_license_received,
|
|
time_since_first_decrypt, time_since_last_decrypt, status,
|
|
reinterpret_cast<uint8_t*>(const_cast<char*>(server_mac_key.data())),
|
|
reinterpret_cast<uint8_t*>(const_cast<char*>(client_mac_key.data())),
|
|
reinterpret_cast<const uint8_t*>(provider_session_token.data()),
|
|
provider_session_token.size());
|
|
metrics_->oemcrypto_create_old_usage_entry_.Increment(result);
|
|
});
|
|
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("OEMCrypto_CreateOldUsageEntry failed: status = %d",
|
|
static_cast<int>(result));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::CopyOldUsageEntry(
|
|
const std::string& provider_session_token) {
|
|
LOGV("Copying old usage entry: id = %u", oec_session_id_);
|
|
|
|
OEMCryptoResult result;
|
|
WithOecWriteLock("CopyOldUsageEntry", [&] {
|
|
result = OEMCrypto_CopyOldUsageEntry(
|
|
oec_session_id_,
|
|
reinterpret_cast<const uint8_t*>(provider_session_token.data()),
|
|
provider_session_token.size());
|
|
metrics_->oemcrypto_copy_old_usage_entry_.Increment(result);
|
|
});
|
|
|
|
return MapOEMCryptoResult(result, COPY_OLD_USAGE_ENTRY_UNKNOWN_ERROR,
|
|
"CopyOldUsageEntry");
|
|
}
|
|
|
|
bool CryptoSession::GetAnalogOutputCapabilities(bool* can_support_output,
|
|
bool* can_disable_output,
|
|
bool* can_support_cgms_a) {
|
|
LOGV("Getting analog output capabilities: id = %u", oec_session_id_);
|
|
uint32_t flags;
|
|
WithOecReadLock("GetAnalogOutputCapabilities", [&] {
|
|
flags = OEMCrypto_GetAnalogOutputFlags(requested_security_level_);
|
|
});
|
|
|
|
if ((flags & OEMCrypto_Unknown_Analog_Output) != 0) return false;
|
|
*can_support_cgms_a = flags & OEMCrypto_Supports_CGMS_A;
|
|
*can_support_output = flags & OEMCrypto_Supports_Analog_Output;
|
|
*can_disable_output = flags & OEMCrypto_Can_Disable_Analog_Ouptput;
|
|
return true;
|
|
}
|
|
|
|
OEMCrypto_Algorithm CryptoSession::GenericSigningAlgorithm(
|
|
CdmSigningAlgorithm algorithm) {
|
|
if (kSigningAlgorithmHmacSha256 == algorithm) {
|
|
return OEMCrypto_HMAC_SHA256;
|
|
} else {
|
|
return kInvalidAlgorithm;
|
|
}
|
|
}
|
|
|
|
OEMCrypto_Algorithm CryptoSession::GenericEncryptionAlgorithm(
|
|
CdmEncryptionAlgorithm algorithm) {
|
|
if (kEncryptionAlgorithmAesCbc128 == algorithm) {
|
|
return OEMCrypto_AES_CBC_128_NO_PADDING;
|
|
} else {
|
|
return kInvalidAlgorithm;
|
|
}
|
|
}
|
|
|
|
size_t CryptoSession::GenericEncryptionBlockSize(
|
|
CdmEncryptionAlgorithm algorithm) {
|
|
if (kEncryptionAlgorithmAesCbc128 == algorithm) {
|
|
return kAes128BlockSize;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
OEMCryptoResult CryptoSession::CopyBufferInChunks(
|
|
const CdmDecryptionParameters& params,
|
|
OEMCrypto_DestBufferDesc full_buffer_descriptor) {
|
|
size_t remaining_encrypt_length = params.encrypt_length;
|
|
uint8_t subsample_flags = OEMCrypto_FirstSubsample;
|
|
|
|
while (remaining_encrypt_length > 0) {
|
|
// Calculate the size of the next chunk and its offset into the original
|
|
// buffer.
|
|
const size_t chunk_size =
|
|
std::min(remaining_encrypt_length, kMaximumChunkSize);
|
|
const size_t additional_offset =
|
|
params.encrypt_length - remaining_encrypt_length;
|
|
|
|
// Update the remaining length of the original buffer only after
|
|
// calculating the new values.
|
|
remaining_encrypt_length -= chunk_size;
|
|
|
|
// Update the destination buffer with the new offset. Because OEMCrypto
|
|
// can modify the OEMCrypto_DestBufferDesc during the call to
|
|
// OEMCrypto_CopyBuffer, (and is known to do so on some platforms) a new
|
|
// OEMCrypto_DestBufferDesc must be allocated for each call.
|
|
OEMCrypto_DestBufferDesc buffer_descriptor = full_buffer_descriptor;
|
|
switch (buffer_descriptor.type) {
|
|
case OEMCrypto_BufferType_Clear:
|
|
buffer_descriptor.buffer.clear.address += additional_offset;
|
|
buffer_descriptor.buffer.clear.max_length -= additional_offset;
|
|
break;
|
|
case OEMCrypto_BufferType_Secure:
|
|
buffer_descriptor.buffer.secure.offset += additional_offset;
|
|
break;
|
|
case OEMCrypto_BufferType_Direct:
|
|
// OEMCrypto_BufferType_Direct does not need modification.
|
|
break;
|
|
}
|
|
|
|
// Re-add "last subsample" flag if this is the last subsample.
|
|
if (remaining_encrypt_length == 0) {
|
|
subsample_flags |= OEMCrypto_LastSubsample;
|
|
}
|
|
|
|
OEMCryptoResult sts;
|
|
WithOecSessionLock("CopyBufferInChunks", [&] {
|
|
M_TIME(sts = OEMCrypto_CopyBuffer(
|
|
oec_session_id_, params.encrypt_buffer + additional_offset,
|
|
chunk_size, &buffer_descriptor, subsample_flags),
|
|
metrics_, oemcrypto_copy_buffer_, sts,
|
|
metrics::Pow2Bucket(chunk_size));
|
|
});
|
|
|
|
if (sts != OEMCrypto_SUCCESS) {
|
|
return sts;
|
|
}
|
|
|
|
// Clear any subsample flags before the next loop iteration.
|
|
subsample_flags = 0;
|
|
}
|
|
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult CryptoSession::DecryptInChunks(
|
|
const CdmDecryptionParameters& params,
|
|
const OEMCrypto_DestBufferDesc& full_buffer_descriptor,
|
|
const OEMCrypto_CENCEncryptPatternDesc& pattern_descriptor,
|
|
size_t max_chunk_size) {
|
|
size_t remaining_encrypt_length = params.encrypt_length;
|
|
uint8_t subsample_flags = (params.subsample_flags & OEMCrypto_FirstSubsample)
|
|
? OEMCrypto_FirstSubsample
|
|
: 0;
|
|
std::vector<uint8_t> iv = *params.iv;
|
|
|
|
const size_t pattern_length_in_bytes =
|
|
(pattern_descriptor.encrypt + pattern_descriptor.skip) * kAes128BlockSize;
|
|
|
|
while (remaining_encrypt_length > 0) {
|
|
// Calculate the size of the next chunk and its offset into the
|
|
// original buffer.
|
|
const size_t chunk_size =
|
|
std::min(remaining_encrypt_length, max_chunk_size);
|
|
const size_t additional_offset =
|
|
params.encrypt_length - remaining_encrypt_length;
|
|
|
|
// Update the remaining length of the original buffer only after
|
|
// calculating the new values.
|
|
remaining_encrypt_length -= chunk_size;
|
|
|
|
// Update the destination buffer with the new offset. Because OEMCrypto
|
|
// can modify the OEMCrypto_DestBufferDesc during the call to
|
|
// OEMCrypto_DecryptCENC, (and is known to do so on some platforms) a new
|
|
// OEMCrypto_DestBufferDesc must be allocated for each call.
|
|
OEMCrypto_DestBufferDesc buffer_descriptor = full_buffer_descriptor;
|
|
switch (buffer_descriptor.type) {
|
|
case OEMCrypto_BufferType_Clear:
|
|
buffer_descriptor.buffer.clear.address += additional_offset;
|
|
buffer_descriptor.buffer.clear.max_length -= additional_offset;
|
|
break;
|
|
case OEMCrypto_BufferType_Secure:
|
|
buffer_descriptor.buffer.secure.offset += additional_offset;
|
|
break;
|
|
case OEMCrypto_BufferType_Direct:
|
|
// OEMCrypto_BufferType_Direct does not need modification.
|
|
break;
|
|
}
|
|
|
|
// Re-add "last subsample" flag if this is the last subsample.
|
|
if (remaining_encrypt_length == 0 &&
|
|
params.subsample_flags & OEMCrypto_LastSubsample) {
|
|
subsample_flags |= OEMCrypto_LastSubsample;
|
|
}
|
|
|
|
// block_offset and pattern_descriptor 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.
|
|
OEMCryptoResult sts;
|
|
WithOecSessionLock("DecryptInChunks", [&] {
|
|
M_TIME(
|
|
sts = OEMCrypto_DecryptCENC(
|
|
oec_session_id_, params.encrypt_buffer + additional_offset,
|
|
chunk_size, params.is_encrypted, &iv.front(), params.block_offset,
|
|
&buffer_descriptor, &pattern_descriptor, subsample_flags),
|
|
metrics_, oemcrypto_decrypt_cenc_, sts,
|
|
metrics::Pow2Bucket(chunk_size));
|
|
});
|
|
|
|
if (sts != OEMCrypto_SUCCESS) {
|
|
return sts;
|
|
}
|
|
|
|
// If we are not yet done, update the IV so that it is valid for the next
|
|
// iteration.
|
|
if (remaining_encrypt_length != 0) {
|
|
if (cipher_mode_ == kCipherModeCtr) {
|
|
// For CTR modes, update the IV depending on how many encrypted blocks
|
|
// we passed. Since we calculated the chunk size to be an even number
|
|
// of crypto blocks and pattern repetitions in size, we can do a
|
|
// simplified calculation for this.
|
|
uint64_t encrypted_blocks_passed = 0;
|
|
if (pattern_length_in_bytes == 0) {
|
|
encrypted_blocks_passed = chunk_size / kAes128BlockSize;
|
|
} else {
|
|
const size_t pattern_repetitions_passed =
|
|
chunk_size / pattern_length_in_bytes;
|
|
encrypted_blocks_passed =
|
|
pattern_repetitions_passed * pattern_descriptor.encrypt;
|
|
}
|
|
IncrementIV(encrypted_blocks_passed, &iv);
|
|
} else if (cipher_mode_ == kCipherModeCbc) {
|
|
// For CBC modes, use the previous ciphertext block.
|
|
|
|
// Stash the last crypto block in the IV. We don't have to handle
|
|
// partial crypto blocks here because we know we broke the buffer into
|
|
// chunks along even crypto block boundaries.
|
|
const uint8_t* const buffer_end =
|
|
params.encrypt_buffer + additional_offset + chunk_size;
|
|
|
|
const uint8_t* block_end = nullptr;
|
|
if (pattern_length_in_bytes == 0) {
|
|
// For cbc1, the last encrypted block is the last block of the
|
|
// subsample.
|
|
block_end = buffer_end;
|
|
} else {
|
|
// For cbcs, we must look for the last encrypted block, which is
|
|
// probably not the last block of the subsample. Luckily, since the
|
|
// 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.
|
|
block_end = buffer_end - kAes128BlockSize * pattern_descriptor.skip;
|
|
}
|
|
|
|
iv.assign(block_end - kAes128BlockSize, block_end);
|
|
}
|
|
}
|
|
|
|
// Clear any subsample flags before the next loop iteration.
|
|
subsample_flags = 0;
|
|
}
|
|
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
void CryptoSession::IncrementIV(uint64_t increase_by,
|
|
std::vector<uint8_t>* iv_out) {
|
|
std::vector<uint8_t>& iv = *iv_out;
|
|
uint64_t* counter_buffer = reinterpret_cast<uint64_t*>(&iv[8]);
|
|
(*counter_buffer) = htonll64(ntohll64(*counter_buffer) + increase_by);
|
|
}
|
|
|
|
template <class Func>
|
|
auto CryptoSession::WithStaticFieldWriteLock(const char* tag, Func body)
|
|
-> decltype(body()) {
|
|
LOGV("Static field write lock: %s", tag);
|
|
std::unique_lock<shared_mutex> auto_lock(static_field_mutex_);
|
|
return body();
|
|
}
|
|
|
|
template <class Func>
|
|
auto CryptoSession::WithStaticFieldReadLock(const char* tag, Func body)
|
|
-> decltype(body()) {
|
|
LOGV("Static field read lock: %s", tag);
|
|
shared_lock<shared_mutex> auto_lock(static_field_mutex_);
|
|
return body();
|
|
}
|
|
|
|
template <class Func>
|
|
auto CryptoSession::WithOecWriteLock(const char* tag, Func body)
|
|
-> decltype(body()) {
|
|
LOGV("OEMCrypto write lock: %s", tag);
|
|
std::unique_lock<shared_mutex> auto_lock(oem_crypto_mutex_);
|
|
return body();
|
|
}
|
|
|
|
template <class Func>
|
|
auto CryptoSession::WithOecReadLock(const char* tag, Func body)
|
|
-> decltype(body()) {
|
|
LOGV("OEMCrypto read lock: %s", tag);
|
|
shared_lock<shared_mutex> auto_lock(oem_crypto_mutex_);
|
|
return body();
|
|
}
|
|
|
|
template <class Func>
|
|
auto CryptoSession::WithOecSessionLock(const char* tag, Func body)
|
|
-> decltype(body()) {
|
|
LOGV("OEMCrypto session lock: %s", tag);
|
|
shared_lock<shared_mutex> oec_auto_lock(oem_crypto_mutex_);
|
|
std::unique_lock<std::mutex> session_auto_lock(oem_crypto_session_mutex_);
|
|
return body();
|
|
}
|
|
|
|
bool CryptoSession::IsInitialized() {
|
|
return WithStaticFieldReadLock("IsInitialized", [] { return initialized_; });
|
|
}
|
|
|
|
// CryptoSesssionFactory support
|
|
std::mutex CryptoSession::factory_mutex_;
|
|
// The factory will either be set by WvCdmTestBase, or a default factory is
|
|
// created on the first call to MakeCryptoSession.
|
|
std::unique_ptr<CryptoSessionFactory> CryptoSession::factory_ =
|
|
std::unique_ptr<CryptoSessionFactory>();
|
|
|
|
CryptoSession* CryptoSession::MakeCryptoSession(
|
|
metrics::CryptoMetrics* crypto_metrics) {
|
|
std::unique_lock<std::mutex> auto_lock(factory_mutex_);
|
|
// If the factory_ has not been set, then use a default factory.
|
|
if (!factory_) factory_.reset(new CryptoSessionFactory());
|
|
return factory_->MakeCryptoSession(crypto_metrics);
|
|
}
|
|
|
|
CryptoSession* CryptoSessionFactory::MakeCryptoSession(
|
|
metrics::CryptoMetrics* crypto_metrics) {
|
|
return new CryptoSession(crypto_metrics);
|
|
}
|
|
|
|
} // namespace wvcdm
|