(This is a merge from http://go/wvgerrit/30220) Previously, extracting the system ID was only supported on Keybox-based systems. This patch adds support for extracting the system ID from the OEM Certificate chain on Provisioning 3.0 devices. This is done by getting the Widevine intermediate cert from the chain, finding the Widevine System ID extension in that cert, and extracting the value. The code that does the extraction is separate from any code that calls OEMCrypto so that it can be unit-tested in isolation. This patch adds a crypto_session_unittest test to do this unit-testing. Bug: 34776194 Test: crypto_session_unittest Change-Id: I3e273968208fb31ae6019ccc383b419625d1ae22
2624 lines
82 KiB
C++
2624 lines
82 KiB
C++
// Copyright 2012 Google Inc. All Rights Reserved.
|
|
//
|
|
// Crypto - wrapper classes for OEMCrypto interface
|
|
//
|
|
|
|
#include "crypto_session.h"
|
|
|
|
#include <arpa/inet.h> // needed for ntoh()
|
|
#include <string.h>
|
|
#include <algorithm>
|
|
#include <iostream>
|
|
#include <memory>
|
|
|
|
#include "crypto_key.h"
|
|
#include "log.h"
|
|
#include "openssl/asn1.h"
|
|
#include "openssl/sha.h"
|
|
#include "openssl/x509v3.h"
|
|
#include "properties.h"
|
|
#include "pst_report.h"
|
|
#include "string_conversions.h"
|
|
#include "usage_table_header.h"
|
|
#include "wv_cdm_constants.h"
|
|
|
|
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;
|
|
const size_t kMaximumChunkSize = 100 * 1024; // 100 KiB
|
|
const size_t kEstimatedInitialUsageTableHeader = 40;
|
|
const size_t kOemCryptoApiVersionSupportsBigUsageTables = 13;
|
|
|
|
// Constants and utility objects relating to OEM Certificates
|
|
const int kExtensionOidSize = 64;
|
|
const char* const kWidevineSystemIdExtensionOid = "1.3.6.1.4.1.11129.4.1.1";
|
|
struct X509StackDeleter {
|
|
void operator()(STACK_OF(X509)* stack) {
|
|
sk_X509_pop_free(stack, X509_free);
|
|
}
|
|
};
|
|
}
|
|
|
|
namespace wvcdm {
|
|
|
|
Lock CryptoSession::crypto_lock_;
|
|
bool CryptoSession::initialized_ = false;
|
|
int CryptoSession::session_count_ = 0;
|
|
uint64_t CryptoSession::request_id_index_ = 0;
|
|
UsageTableHeader* CryptoSession::usage_table_header_l1_ = NULL;
|
|
UsageTableHeader* CryptoSession::usage_table_header_l3_ = NULL;
|
|
|
|
CryptoSession::CryptoSession(metrics::CryptoMetrics* metrics)
|
|
: metrics_(metrics),
|
|
open_(false),
|
|
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_(kNonSecureUsageSupport),
|
|
usage_table_header_(NULL),
|
|
request_id_base_(0),
|
|
cipher_mode_(kCipherModeCtr) {
|
|
Init();
|
|
life_span_.Start();
|
|
}
|
|
|
|
CryptoSession::~CryptoSession() {
|
|
if (open_) {
|
|
Close();
|
|
}
|
|
Terminate();
|
|
M_RECORD(metrics_, crypto_session_life_span_, life_span_.AsMs());
|
|
}
|
|
|
|
bool CryptoSession::GetProvisioningMethod(CdmClientTokenType* token_type) {
|
|
OEMCrypto_ProvisioningMethod method;
|
|
switch (method = OEMCrypto_GetProvisioningMethod(requested_security_level_)) {
|
|
case OEMCrypto_OEMCertificate:
|
|
*token_type = kClientTokenOemCert;
|
|
break;
|
|
case OEMCrypto_Keybox:
|
|
*token_type = kClientTokenKeybox;
|
|
break;
|
|
case OEMCrypto_DrmCertificate:
|
|
*token_type = kClientTokenDrmCert;
|
|
break;
|
|
case OEMCrypto_ProvisioningError:
|
|
default:
|
|
LOGE("OEMCrypto_GetProvisioningMethod failed", method);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void CryptoSession::Init() {
|
|
LOGV("CryptoSession::Init");
|
|
AutoLock auto_lock(crypto_lock_);
|
|
session_count_ += 1;
|
|
if (!initialized_) {
|
|
OEMCryptoResult sts;
|
|
M_TIME(
|
|
sts = OEMCrypto_Initialize(),
|
|
metrics_,
|
|
oemcrypto_initialize_,
|
|
sts);
|
|
if (OEMCrypto_SUCCESS != sts) {
|
|
LOGE("OEMCrypto_Initialize failed: %d", sts);
|
|
return;
|
|
}
|
|
initialized_ = true;
|
|
}
|
|
if (!GetProvisioningMethod(&pre_provision_token_type_)) {
|
|
initialized_ = false;
|
|
}
|
|
}
|
|
|
|
void CryptoSession::Terminate() {
|
|
LOGV("CryptoSession::Terminate: initialized_=%d, session_count_=%d",
|
|
initialized_, session_count_);
|
|
AutoLock auto_lock(crypto_lock_);
|
|
if (session_count_ > 0) {
|
|
session_count_ -= 1;
|
|
} else {
|
|
LOGE("CryptoSession::Terminate error, session count: %d", session_count_);
|
|
}
|
|
if (session_count_ > 0 || !initialized_) return;
|
|
OEMCryptoResult sts = OEMCrypto_Terminate();
|
|
if (OEMCrypto_SUCCESS != sts) {
|
|
LOGE("OEMCrypto_Terminate failed: %d", sts);
|
|
}
|
|
|
|
if (usage_table_header_l1_ != NULL) {
|
|
delete usage_table_header_l1_;
|
|
usage_table_header_l1_ = NULL;
|
|
}
|
|
if (usage_table_header_l3_ != NULL) {
|
|
delete usage_table_header_l3_;
|
|
usage_table_header_l3_ = NULL;
|
|
}
|
|
|
|
initialized_ = false;
|
|
}
|
|
|
|
bool CryptoSession::GetTokenFromKeybox(std::string* token) {
|
|
std::string temp_buffer(KEYBOX_KEY_DATA_SIZE, '\0');
|
|
// lock is held by caller
|
|
size_t buf_size = temp_buffer.size();
|
|
uint8_t* buf = reinterpret_cast<uint8_t*>(&temp_buffer[0]);
|
|
|
|
OEMCryptoResult status;
|
|
M_TIME(
|
|
status = OEMCrypto_GetKeyData(
|
|
buf,
|
|
&buf_size,
|
|
requested_security_level_),
|
|
metrics_,
|
|
oemcrypto_get_key_data_,
|
|
status,
|
|
metrics::Pow2Bucket(buf_size),
|
|
requested_security_level_);
|
|
if (OEMCrypto_SUCCESS == status) {
|
|
token->swap(temp_buffer);
|
|
return true;
|
|
}
|
|
LOGE("CryptoSession::GetTokenFromKeybox : error %d.", status);
|
|
return false;
|
|
}
|
|
|
|
bool CryptoSession::GetTokenFromOemCert(std::string* token) {
|
|
if (token == NULL) {
|
|
LOGE("CryptoSession::GetTokenFromOemCert: token not provided ");
|
|
return false;
|
|
}
|
|
OEMCryptoResult status;
|
|
if (!oem_token_.empty()) {
|
|
token->assign(oem_token_);
|
|
return true;
|
|
}
|
|
std::string temp_buffer(CERTIFICATE_DATA_SIZE, '\0');
|
|
// lock is held by caller
|
|
bool retrying = false;
|
|
while (true) {
|
|
size_t buf_size = temp_buffer.size();
|
|
uint8_t* buf = reinterpret_cast<uint8_t*>(&temp_buffer[0]);
|
|
status = OEMCrypto_GetOEMPublicCertificate(oec_session_id_, buf, &buf_size);
|
|
if (OEMCrypto_SUCCESS == status) {
|
|
temp_buffer.resize(buf_size);
|
|
oem_token_.assign(temp_buffer);
|
|
token->assign(temp_buffer);
|
|
return true;
|
|
}
|
|
if (OEMCrypto_ERROR_SHORT_BUFFER && !retrying) {
|
|
temp_buffer.resize(buf_size);
|
|
retrying = true;
|
|
continue;
|
|
}
|
|
LOGE("CryptoSession::GetTokenFromOemCert : error %d.", status);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool CryptoSession::GetClientToken(std::string* token) {
|
|
if (!token) {
|
|
LOGE("CryptoSession::GetClientToken : No token passed to method.");
|
|
return false;
|
|
}
|
|
LOGV("CryptoSession::GetClientToken: Lock");
|
|
AutoLock auto_lock(crypto_lock_);
|
|
if (!initialized_) {
|
|
return false;
|
|
}
|
|
// Only keybox is used for client token. All other cases use DRM Cert.
|
|
if (pre_provision_token_type_ != kClientTokenKeybox) {
|
|
return false;
|
|
}
|
|
return GetTokenFromKeybox(token);
|
|
}
|
|
|
|
bool CryptoSession::GetProvisioningToken(std::string* token) {
|
|
if (!token) {
|
|
LOGE("CryptoSession::GetProvisioningToken : No token passed to method.");
|
|
return false;
|
|
}
|
|
LOGV("CryptoSession::GetProvisioningToken: Lock");
|
|
AutoLock auto_lock(crypto_lock_);
|
|
|
|
if (!initialized_) {
|
|
return false;
|
|
}
|
|
|
|
if (pre_provision_token_type_ == kClientTokenKeybox) {
|
|
return GetTokenFromKeybox(token);
|
|
} else if (pre_provision_token_type_ == kClientTokenOemCert) {
|
|
return GetTokenFromOemCert(token);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
CdmSecurityLevel CryptoSession::GetSecurityLevel() {
|
|
LOGV("CryptoSession::GetSecurityLevel");
|
|
if (!initialized_) {
|
|
M_RECORD(metrics_, oemcrypto_security_level_, 0,
|
|
kSecurityLevelUninitialized, requested_security_level_);
|
|
return kSecurityLevelUninitialized;
|
|
}
|
|
|
|
wvcdm::metrics::TimerMetric timer;
|
|
timer.Start();
|
|
|
|
std::string security_level =
|
|
OEMCrypto_SecurityLevel(requested_security_level_);
|
|
double clock_time = timer.AsUs();
|
|
|
|
if ((security_level.size() != 2) || (security_level.at(0) != 'L')) {
|
|
M_RECORD(metrics_, oemcrypto_security_level_, clock_time,
|
|
kSecurityLevelUnknown, requested_security_level_);
|
|
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;
|
|
}
|
|
|
|
M_RECORD(metrics_, oemcrypto_security_level_, clock_time,
|
|
cdm_security_level, requested_security_level_);
|
|
return cdm_security_level;
|
|
}
|
|
|
|
bool CryptoSession::GetInternalDeviceUniqueId(std::string* device_id) {
|
|
if (!device_id) {
|
|
LOGE("CryptoSession::GetInternalDeviceUniqueId : No buffer passed to "
|
|
"method.");
|
|
return false;
|
|
}
|
|
|
|
LOGV("CryptoSession::GetInternalDeviceUniqueId: Lock");
|
|
AutoLock auto_lock(crypto_lock_);
|
|
if (!initialized_) {
|
|
return false;
|
|
}
|
|
|
|
if (pre_provision_token_type_ == kClientTokenOemCert) {
|
|
return GetTokenFromOemCert(device_id);
|
|
} else {
|
|
std::vector<uint8_t> id;
|
|
size_t id_length = 32;
|
|
id.resize(id_length);
|
|
|
|
OEMCryptoResult sts;
|
|
M_TIME(
|
|
sts = OEMCrypto_GetDeviceID(
|
|
&id[0],
|
|
&id_length,
|
|
requested_security_level_),
|
|
metrics_,
|
|
oemcrypto_get_device_id_,
|
|
sts,
|
|
requested_security_level_);
|
|
|
|
if (OEMCrypto_SUCCESS != sts) {
|
|
return false;
|
|
}
|
|
|
|
device_id->assign(reinterpret_cast<char*>(&id[0]), id_length);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool CryptoSession::GetExternalDeviceUniqueId(std::string* device_id) {
|
|
std::string temp;
|
|
if (!GetInternalDeviceUniqueId(&temp)) return false;
|
|
|
|
if (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.
|
|
uint8_t hash[SHA256_DIGEST_LENGTH];
|
|
SHA256_CTX ctx;
|
|
SHA256_Init(&ctx);
|
|
SHA256_Update(&ctx, temp.data(), temp.length());
|
|
SHA256_Final(hash, &ctx);
|
|
|
|
temp.assign(reinterpret_cast<char*>(hash), SHA256_DIGEST_LENGTH);
|
|
}
|
|
|
|
*device_id = temp;
|
|
return true;
|
|
}
|
|
|
|
bool CryptoSession::GetApiVersion(uint32_t* version) {
|
|
if (!version) {
|
|
LOGE("CryptoSession::GetApiVersion: No buffer passed to method.");
|
|
return false;
|
|
}
|
|
|
|
if (!initialized_) {
|
|
return false;
|
|
}
|
|
M_TIME(
|
|
*version = OEMCrypto_APIVersion(requested_security_level_),
|
|
metrics_,
|
|
oemcrypto_api_version_,
|
|
*version,
|
|
requested_security_level_);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CryptoSession::GetSystemId(uint32_t* system_id) {
|
|
if (!system_id) {
|
|
LOGE("CryptoSession::GetSystemId: No buffer passed to method.");
|
|
return false;
|
|
}
|
|
|
|
LOGV("CryptoSession::GetSystemId: Lock");
|
|
AutoLock auto_lock(crypto_lock_);
|
|
if (!initialized_) {
|
|
return false;
|
|
}
|
|
|
|
if (pre_provision_token_type_ == kClientTokenKeybox) {
|
|
uint8_t buf[KEYBOX_KEY_DATA_SIZE];
|
|
size_t buf_size = sizeof(buf);
|
|
|
|
OEMCryptoResult sts;
|
|
M_TIME(
|
|
sts = OEMCrypto_GetKeyData(
|
|
buf,
|
|
&buf_size,
|
|
requested_security_level_),
|
|
metrics_,
|
|
oemcrypto_get_key_data_,
|
|
sts,
|
|
metrics::Pow2Bucket(buf_size),
|
|
requested_security_level_);
|
|
|
|
if (OEMCrypto_SUCCESS != sts) {
|
|
LOGE("CryptoSession::GetSystemId: OEMCrypto_GetKeyData failed with %d",
|
|
sts);
|
|
return false;
|
|
}
|
|
|
|
// Decode 32-bit int encoded as network-byte-order byte array starting at
|
|
// index 4.
|
|
uint32_t* id = reinterpret_cast<uint32_t*>(&buf[4]);
|
|
|
|
*system_id = ntohl(*id);
|
|
return true;
|
|
} else if (pre_provision_token_type_ == kClientTokenOemCert) {
|
|
// Get the OEM Cert
|
|
std::string oem_cert;
|
|
if (GetTokenFromOemCert(&oem_cert)) {
|
|
return ExtractSystemIdFromOemCert(oem_cert, system_id);
|
|
} else {
|
|
return false;
|
|
}
|
|
} else {
|
|
LOGE("CryptoSession::GetSystemId: Unsupported pre-provision token type %d",
|
|
pre_provision_token_type_);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool CryptoSession::ExtractSystemIdFromOemCert(const std::string& oem_cert,
|
|
uint32_t* system_id) {
|
|
// Parse the OEM Cert chain into an OpenSSL X509 Stack
|
|
const std::unique_ptr<STACK_OF(X509), X509StackDeleter>
|
|
x509_stack(sk_X509_new_null());
|
|
if (!x509_stack) {
|
|
LOGE("CryptoSession::GetSystemId: Unable to allocate X509 Stack.");
|
|
return false;
|
|
}
|
|
|
|
CBS pkcs7;
|
|
CBS_init(&pkcs7, reinterpret_cast<const uint8_t*>(oem_cert.data()),
|
|
oem_cert.size());
|
|
if (!PKCS7_get_certificates(x509_stack.get(), &pkcs7)) {
|
|
LOGE("CryptoSession::GetSystemId: Error getting certificate chain.");
|
|
return false;
|
|
}
|
|
|
|
// Get the Widevine intermediate cert from the stack
|
|
if (sk_X509_num(x509_stack.get()) != 2) {
|
|
LOGE("CryptoSession::GetSystemId: Expected 2 certificates in chain, got %d",
|
|
sk_X509_num(x509_stack.get()));
|
|
return false;
|
|
}
|
|
|
|
X509* const intermediate_cert = sk_X509_value(x509_stack.get(), 1);
|
|
if (!intermediate_cert) {
|
|
LOGE("CryptoSession::GetSystemId: Unable to get intermediate cert.");
|
|
return false;
|
|
}
|
|
|
|
// Find the Widevine System ID extension in the intermediate cert
|
|
const int extension_count = X509_get_ext_count(intermediate_cert);
|
|
for (int i = 0; i < extension_count; ++i) {
|
|
X509_EXTENSION* const extension = X509_get_ext(intermediate_cert, i);
|
|
if (!extension) {
|
|
LOGE("CryptoSession::GetSystemId: Unable to get cert extension %d", i);
|
|
continue;
|
|
}
|
|
|
|
ASN1_OBJECT* const extension_object = X509_EXTENSION_get_object(extension);
|
|
if (!extension_object) {
|
|
LOGE("CryptoSession::GetSystemId: Unable to get object of cert "
|
|
"extension %d", i);
|
|
continue;
|
|
}
|
|
|
|
char extension_name[kExtensionOidSize + 1];
|
|
OBJ_obj2txt(extension_name, kExtensionOidSize, extension_object, 1);
|
|
if (strcmp(extension_name, kWidevineSystemIdExtensionOid) != 0) {
|
|
// This extension is not the Widevine System ID, so we should move on to
|
|
// the next one.
|
|
continue;
|
|
}
|
|
|
|
ASN1_OCTET_STRING* const octet_str = X509_EXTENSION_get_data(extension);
|
|
if (!octet_str) {
|
|
LOGE("CryptoSession::GetSystemId: Unable to get data of Widevine System "
|
|
"ID extension.");
|
|
return false;
|
|
}
|
|
|
|
const unsigned char* data = octet_str->data;
|
|
if (!data) {
|
|
LOGE("CryptoSession::GetSystemId: Null data in Widevine System ID "
|
|
"extension.");
|
|
return false;
|
|
}
|
|
|
|
ASN1_INTEGER* const asn1_integer =
|
|
d2i_ASN1_INTEGER(NULL, &data, octet_str->length);
|
|
if (!asn1_integer) {
|
|
LOGE("CryptoSession::GetSystemId: Unable to decode data in Widevine "
|
|
"System ID extension.");
|
|
return false;
|
|
}
|
|
|
|
const long system_id_long = ASN1_INTEGER_get(asn1_integer);
|
|
ASN1_INTEGER_free(asn1_integer);
|
|
if (system_id_long == -1) {
|
|
LOGE("CryptoSession::GetSystemId: Unable to decode ASN integer in "
|
|
"Widevine System ID extension.");
|
|
return false;
|
|
}
|
|
|
|
*system_id = static_cast<uint32_t>(system_id_long);
|
|
return true;
|
|
}
|
|
|
|
LOGE("CryptoSession::GetSystemId: Widevine System ID extension not found.");
|
|
return false;
|
|
}
|
|
|
|
|
|
bool CryptoSession::GetProvisioningId(std::string* provisioning_id) {
|
|
if (!provisioning_id) {
|
|
LOGE("CryptoSession::GetProvisioningId : No buffer passed to method.");
|
|
return false;
|
|
}
|
|
|
|
uint8_t buf[KEYBOX_KEY_DATA_SIZE];
|
|
size_t buf_size = sizeof(buf);
|
|
|
|
LOGV("CryptoSession::GetProvisioningId: Lock");
|
|
AutoLock auto_lock(crypto_lock_);
|
|
if (!initialized_) {
|
|
return false;
|
|
}
|
|
|
|
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.
|
|
if (!GetExternalDeviceUniqueId(provisioning_id)) return false;
|
|
|
|
for (size_t i = 0; i < provisioning_id->size(); ++i) {
|
|
char value = (*provisioning_id)[i];
|
|
(*provisioning_id)[i] = ~value;
|
|
}
|
|
|
|
return true;
|
|
} else {
|
|
OEMCryptoResult sts;
|
|
M_TIME(
|
|
sts = OEMCrypto_GetKeyData(
|
|
buf,
|
|
&buf_size,
|
|
requested_security_level_),
|
|
metrics_,
|
|
oemcrypto_get_key_data_,
|
|
sts,
|
|
metrics::Pow2Bucket(buf_size),
|
|
requested_security_level_);
|
|
if (OEMCrypto_SUCCESS != sts) {
|
|
return false;
|
|
}
|
|
|
|
provisioning_id->assign(reinterpret_cast<char*>(&buf[8]), 16);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
uint8_t CryptoSession::GetSecurityPatchLevel() {
|
|
uint8_t patch;
|
|
M_TIME(
|
|
patch = OEMCrypto_Security_Patch_Level(
|
|
requested_security_level_),
|
|
metrics_,
|
|
oemcrypto_security_patch_level_,
|
|
patch,
|
|
requested_security_level_);
|
|
return patch;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::Open(SecurityLevel requested_security_level) {
|
|
LOGD("CryptoSession::Open: Lock: requested_security_level: %s",
|
|
requested_security_level == kLevel3
|
|
? QUERY_VALUE_SECURITY_LEVEL_L3.c_str()
|
|
: QUERY_VALUE_SECURITY_LEVEL_DEFAULT.c_str());
|
|
AutoLock auto_lock(crypto_lock_);
|
|
if (!initialized_) return UNKNOWN_ERROR;
|
|
if (open_) return NO_ERROR;
|
|
|
|
OEMCrypto_SESSION sid;
|
|
requested_security_level_ = requested_security_level;
|
|
OEMCryptoResult sts;
|
|
M_TIME(
|
|
sts = OEMCrypto_OpenSession(
|
|
&sid,
|
|
requested_security_level),
|
|
metrics_,
|
|
oemcrypto_open_session_,
|
|
sts,
|
|
requested_security_level);
|
|
if (OEMCrypto_SUCCESS == sts) {
|
|
oec_session_id_ = static_cast<CryptoSessionId>(sid);
|
|
LOGV("OpenSession: id= %ld", (uint32_t)oec_session_id_);
|
|
open_ = true;
|
|
} else if (OEMCrypto_ERROR_TOO_MANY_SESSIONS == sts) {
|
|
LOGE("OEMCrypto_Open failed: %d, open sessions: %ld, initialized: %d",
|
|
sts, session_count_, (int)initialized_);
|
|
return INSUFFICIENT_CRYPTO_RESOURCES;
|
|
}
|
|
if (!open_) {
|
|
LOGE("OEMCrypto_Open failed: %d, open sessions: %ld, initialized: %d",
|
|
sts, session_count_, (int)initialized_);
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
OEMCryptoResult random_sts;
|
|
M_TIME(
|
|
random_sts = OEMCrypto_GetRandom(
|
|
reinterpret_cast<uint8_t*>(&request_id_base_),
|
|
sizeof(request_id_base_)),
|
|
metrics_,
|
|
oemcrypto_get_random_,
|
|
random_sts,
|
|
metrics::Pow2Bucket(sizeof(request_id_base_)));
|
|
++request_id_index_;
|
|
|
|
CdmUsageSupportType usage_support_type;
|
|
if (GetUsageSupportType(&usage_support_type) == NO_ERROR) {
|
|
if (usage_support_type == kUsageEntrySupport) {
|
|
CdmSecurityLevel security_level = GetSecurityLevel();
|
|
if (security_level == kSecurityLevelL1 ||
|
|
security_level == kSecurityLevelL3) {
|
|
UsageTableHeader* header = security_level == kSecurityLevelL1 ?
|
|
usage_table_header_l1_ : usage_table_header_l3_;
|
|
if (header == NULL) {
|
|
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.
|
|
if (!header->Init(security_level, this)) {
|
|
delete header;
|
|
usage_table_header_ = NULL;
|
|
return NO_ERROR;
|
|
}
|
|
if (security_level == kSecurityLevelL1)
|
|
usage_table_header_l1_ = header;
|
|
else
|
|
usage_table_header_l3_ = header;
|
|
}
|
|
usage_table_header_ = header;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
void CryptoSession::Close() {
|
|
LOGV("CloseSession: id=%ld open=%s", (uint32_t)oec_session_id_,
|
|
open_ ? "true" : "false");
|
|
|
|
OEMCryptoResult close_sts;
|
|
bool update_usage_table = false;
|
|
{
|
|
AutoLock auto_lock(crypto_lock_);
|
|
if (!open_) return;
|
|
|
|
M_TIME(
|
|
close_sts = OEMCrypto_CloseSession(
|
|
oec_session_id_),
|
|
metrics_,
|
|
oemcrypto_close_session_,
|
|
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) {
|
|
UpdateUsageInformation();
|
|
}
|
|
}
|
|
|
|
bool CryptoSession::GenerateRequestId(std::string* req_id_str) {
|
|
LOGV("CryptoSession::GenerateRequestId: Lock");
|
|
AutoLock auto_lock(crypto_lock_);
|
|
if (!req_id_str) {
|
|
LOGE("CryptoSession::GenerateRequestId: No output destination provided.");
|
|
return false;
|
|
}
|
|
|
|
*req_id_str = HexEncode(reinterpret_cast<uint8_t*>(&request_id_base_),
|
|
sizeof(request_id_base_)) +
|
|
HexEncode(reinterpret_cast<uint8_t*>(&request_id_index_),
|
|
sizeof(request_id_index_));
|
|
return true;
|
|
}
|
|
|
|
bool CryptoSession::PrepareRequest(const std::string& message,
|
|
bool is_provisioning,
|
|
std::string* signature) {
|
|
LOGV("CryptoSession::PrepareRequest: Lock");
|
|
AutoLock auto_lock(crypto_lock_);
|
|
|
|
if (!signature) {
|
|
LOGE("CryptoSession::PrepareRequest : No output destination provided.");
|
|
return false;
|
|
}
|
|
|
|
if (!Properties::use_certificates_as_identification() ||
|
|
(is_provisioning && (pre_provision_token_type_ == kClientTokenKeybox))) {
|
|
if (!GenerateDerivedKeys(message)) return false;
|
|
|
|
if (!GenerateSignature(message, signature)) return false;
|
|
} else {
|
|
if (!GenerateRsaSignature(message, signature)) return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CryptoSession::PrepareRenewalRequest(const std::string& message,
|
|
std::string* signature) {
|
|
LOGV("CryptoSession::PrepareRenewalRequest: Lock");
|
|
AutoLock auto_lock(crypto_lock_);
|
|
|
|
if (!signature) {
|
|
LOGE(
|
|
"CryptoSession::PrepareRenewalRequest : No output destination "
|
|
"provided.");
|
|
return false;
|
|
}
|
|
|
|
if (!GenerateSignature(message, signature)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CryptoSession::GenerateMacContext(const std::string& input_context,
|
|
std::string* deriv_context) {
|
|
if (!deriv_context) {
|
|
LOGE("CryptoSession::GenerateMacContext : No output destination provided.");
|
|
return;
|
|
}
|
|
|
|
const std::string kSigningKeyLabel = "AUTHENTICATION";
|
|
const size_t kSigningKeySizeBits = 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 CryptoSession::GenerateEncryptContext(const std::string& input_context,
|
|
std::string* deriv_context) {
|
|
if (!deriv_context) {
|
|
LOGE(
|
|
"CryptoSession::GenerateEncryptContext : No output destination "
|
|
"provided.");
|
|
return;
|
|
}
|
|
|
|
const std::string kEncryptionKeyLabel = "ENCRYPTION";
|
|
const size_t kEncryptionKeySizeBits = KEY_SIZE * 8;
|
|
|
|
deriv_context->assign(kEncryptionKeyLabel);
|
|
deriv_context->append(1, '\0');
|
|
deriv_context->append(input_context);
|
|
deriv_context->append(EncodeUint32(kEncryptionKeySizeBits));
|
|
}
|
|
|
|
size_t CryptoSession::GetOffset(std::string message, std::string field) {
|
|
size_t pos = message.find(field);
|
|
if (pos == std::string::npos) {
|
|
LOGE("CryptoSession::GetOffset : Cannot find offset for %s", field.c_str());
|
|
pos = 0;
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
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) {
|
|
LOGV("CryptoSession::LoadKeys: Lock");
|
|
AutoLock auto_lock(crypto_lock_);
|
|
|
|
const uint8_t* msg = reinterpret_cast<const uint8_t*>(message.data());
|
|
const uint8_t* enc_mac_key = NULL;
|
|
const uint8_t* enc_mac_key_iv = NULL;
|
|
if (mac_key.size() >= MAC_KEY_SIZE && mac_key_iv.size() >= KEY_IV_SIZE) {
|
|
enc_mac_key = msg + GetOffset(message, mac_key);
|
|
enc_mac_key_iv = msg + GetOffset(message, mac_key_iv);
|
|
} else {
|
|
LOGV("CryptoSession::LoadKeys: enc_mac_key not set");
|
|
}
|
|
std::vector<OEMCrypto_KeyObject> load_keys(keys.size());
|
|
for (size_t i = 0; i < keys.size(); ++i) {
|
|
const CryptoKey* ki = &keys[i];
|
|
OEMCrypto_KeyObject* ko = &load_keys[i];
|
|
ko->key_id = msg + GetOffset(message, ki->key_id());
|
|
ko->key_id_length = ki->key_id().length();
|
|
ko->key_data_iv = msg + GetOffset(message, ki->key_data_iv());
|
|
ko->key_data = msg + GetOffset(message, ki->key_data());
|
|
ko->key_data_length = ki->key_data().length();
|
|
if (ki->HasKeyControl()) {
|
|
ko->key_control_iv = msg + GetOffset(message, ki->key_control_iv());
|
|
ko->key_control = msg + GetOffset(message, ki->key_control());
|
|
} else {
|
|
LOGE("For key %d: XXX key has no control block. size=%d", i,
|
|
ki->key_control().size());
|
|
ko->key_control_iv = NULL;
|
|
ko->key_control = NULL;
|
|
}
|
|
ko->cipher_mode = ki->cipher_mode() == kCipherModeCbc
|
|
? OEMCrypto_CipherMode_CBC
|
|
: OEMCrypto_CipherMode_CTR;
|
|
cipher_mode_ = ki->cipher_mode();
|
|
}
|
|
uint8_t* pst = NULL;
|
|
if (!provider_session_token.empty()) {
|
|
pst = const_cast<uint8_t*>(msg) + GetOffset(message, provider_session_token);
|
|
}
|
|
uint8_t* srm_req = NULL;
|
|
if (!srm_requirement.empty())
|
|
srm_req = const_cast<uint8_t*>(msg) + GetOffset(message, srm_requirement);
|
|
|
|
LOGV("LoadKeys: id=%ld", (uint32_t)oec_session_id_);
|
|
OEMCryptoResult sts;
|
|
M_TIME(
|
|
sts = OEMCrypto_LoadKeys(
|
|
oec_session_id_,
|
|
msg,
|
|
message.size(),
|
|
reinterpret_cast<const uint8_t*>(signature.data()),
|
|
signature.size(),
|
|
enc_mac_key_iv,
|
|
enc_mac_key,
|
|
keys.size(),
|
|
&load_keys[0],
|
|
pst,
|
|
provider_session_token.length(),
|
|
srm_req),
|
|
metrics_,
|
|
oemcrypto_load_keys_,
|
|
sts);
|
|
CdmResponseType result = KEY_ADDED;
|
|
if (OEMCrypto_SUCCESS == sts) {
|
|
if (!provider_session_token.empty())
|
|
update_usage_table_after_close_session_ = true;
|
|
result = KEY_ADDED;
|
|
} else if (OEMCrypto_ERROR_TOO_MANY_KEYS == sts) {
|
|
LOGE("CryptoSession::LoadKeys: OEMCrypto_LoadKeys error=%d", sts);
|
|
result = INSUFFICIENT_CRYPTO_RESOURCES;
|
|
} else if (OEMCrypto_ERROR_USAGE_TABLE_UNRECOVERABLE == sts) {
|
|
// Handle vendor specific error
|
|
LOGE("CryptoSession::LoadKeys: OEMCrypto_LoadKeys error=%d", sts);
|
|
result = NEED_PROVISIONING;
|
|
} else {
|
|
LOGE("CryptoSession::LoadKeys: OEMCrypto_LoadKeys error=%d", sts);
|
|
result = LOAD_KEY_ERROR;
|
|
}
|
|
|
|
// Leaving critical section
|
|
crypto_lock_.Release();
|
|
|
|
if (!provider_session_token.empty())
|
|
UpdateUsageInformation();
|
|
return result;
|
|
}
|
|
|
|
bool CryptoSession::LoadCertificatePrivateKey(std::string& wrapped_key) {
|
|
LOGV("CryptoSession::LoadCertificatePrivateKey: Lock");
|
|
AutoLock auto_lock(crypto_lock_);
|
|
|
|
// 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 =
|
|
OEMCrypto_GetOEMPublicCertificate(oec_session_id_, buf, &buf_size);
|
|
|
|
LOGV("LoadDeviceRSAKey: id=%ld", (uint32_t)oec_session_id_);
|
|
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);
|
|
if (OEMCrypto_SUCCESS != sts) {
|
|
LOGE("LoadCertificatePrivateKey: OEMCrypto_LoadDeviceRSAKey error=%d", sts);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CryptoSession::RefreshKeys(const std::string& message,
|
|
const std::string& signature, int num_keys,
|
|
const CryptoKey* key_array) {
|
|
LOGV("CryptoSession::RefreshKeys: Lock");
|
|
AutoLock auto_lock(crypto_lock_);
|
|
|
|
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];
|
|
if (ki->key_id().empty()) {
|
|
ko->key_id = NULL;
|
|
} else {
|
|
ko->key_id = msg + GetOffset(message, ki->key_id());
|
|
}
|
|
if (ki->HasKeyControl()) {
|
|
if (ki->key_control_iv().empty()) {
|
|
ko->key_control_iv = NULL;
|
|
} else {
|
|
ko->key_control_iv = msg + GetOffset(message, ki->key_control_iv());
|
|
}
|
|
ko->key_control = msg + GetOffset(message, ki->key_control());
|
|
} else {
|
|
ko->key_control_iv = NULL;
|
|
ko->key_control = NULL;
|
|
}
|
|
}
|
|
LOGV("RefreshKeys: id=%ld", static_cast<uint32_t>(oec_session_id_));
|
|
OEMCryptoResult refresh_sts;
|
|
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);
|
|
return OEMCrypto_SUCCESS == refresh_sts;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::SelectKey(const std::string& key_id) {
|
|
// Crypto session lock already locked.
|
|
if (!cached_key_id_.empty() && cached_key_id_ == key_id) {
|
|
// Already using the desired key.
|
|
return NO_ERROR;
|
|
}
|
|
|
|
cached_key_id_ = key_id;
|
|
|
|
const uint8_t* key_id_string =
|
|
reinterpret_cast<const uint8_t*>(cached_key_id_.data());
|
|
|
|
OEMCryptoResult sts;
|
|
|
|
M_TIME(
|
|
sts = OEMCrypto_SelectKey(
|
|
oec_session_id_,
|
|
key_id_string,
|
|
cached_key_id_.size()),
|
|
metrics_,
|
|
oemcrypto_select_key_,
|
|
sts);
|
|
|
|
|
|
if (OEMCrypto_SUCCESS != sts) {
|
|
cached_key_id_.clear();
|
|
}
|
|
|
|
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_ERROR_INSUFFICIENT_RESOURCES:
|
|
return INSUFFICIENT_CRYPTO_RESOURCES_2;
|
|
case OEMCrypto_ERROR_UNKNOWN_FAILURE:
|
|
return UNKNOWN_SELECT_KEY_ERROR_1;
|
|
case OEMCrypto_ERROR_CONTROL_INVALID:
|
|
case OEMCrypto_ERROR_KEYBOX_INVALID:
|
|
default:
|
|
return UNKNOWN_SELECT_KEY_ERROR_2;
|
|
}
|
|
}
|
|
|
|
bool CryptoSession::GenerateDerivedKeys(const std::string& message) {
|
|
std::string mac_deriv_message;
|
|
std::string enc_deriv_message;
|
|
GenerateMacContext(message, &mac_deriv_message);
|
|
GenerateEncryptContext(message, &enc_deriv_message);
|
|
|
|
LOGV("GenerateDerivedKeys: id=%ld", (uint32_t)oec_session_id_);
|
|
OEMCryptoResult sts;
|
|
M_TIME(
|
|
sts = OEMCrypto_GenerateDerivedKeys(
|
|
oec_session_id_,
|
|
reinterpret_cast<const uint8_t*>(mac_deriv_message.data()),
|
|
mac_deriv_message.size(),
|
|
reinterpret_cast<const uint8_t*>(enc_deriv_message.data()),
|
|
enc_deriv_message.size()),
|
|
metrics_,
|
|
oemcrypto_generate_derived_keys_,
|
|
sts);
|
|
if (OEMCrypto_SUCCESS != sts) {
|
|
LOGE("GenerateDerivedKeys: OEMCrypto_GenerateDerivedKeys error=%d", sts);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CryptoSession::GenerateDerivedKeys(const std::string& message,
|
|
const std::string& session_key) {
|
|
std::string mac_deriv_message;
|
|
std::string enc_deriv_message;
|
|
GenerateMacContext(message, &mac_deriv_message);
|
|
GenerateEncryptContext(message, &enc_deriv_message);
|
|
|
|
LOGV("GenerateDerivedKeys: id=%ld", (uint32_t)oec_session_id_);
|
|
OEMCryptoResult sts;
|
|
M_TIME(
|
|
sts = OEMCrypto_DeriveKeysFromSessionKey(
|
|
oec_session_id_,
|
|
reinterpret_cast<const uint8_t*>(session_key.data()),
|
|
session_key.size(),
|
|
reinterpret_cast<const uint8_t*>(mac_deriv_message.data()),
|
|
mac_deriv_message.size(),
|
|
reinterpret_cast<const uint8_t*>(enc_deriv_message.data()),
|
|
enc_deriv_message.size()),
|
|
metrics_,
|
|
oemcrypto_derive_keys_from_session_key_,
|
|
sts);
|
|
|
|
if (OEMCrypto_SUCCESS != sts) {
|
|
LOGE("GenerateDerivedKeys: OEMCrypto_DeriveKeysFromSessionKey err=%d", sts);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CryptoSession::GenerateSignature(const std::string& message,
|
|
std::string* signature) {
|
|
LOGV("GenerateSignature: id=%ld", (uint32_t)oec_session_id_);
|
|
if (!signature) {
|
|
LOGE("GenerateSignature: null signature string");
|
|
return false;
|
|
}
|
|
|
|
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) {
|
|
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 true;
|
|
}
|
|
if (OEMCrypto_ERROR_SHORT_BUFFER != sts) {
|
|
break;
|
|
}
|
|
|
|
// Retry with proper-sized signature buffer
|
|
signature->resize(length);
|
|
}
|
|
|
|
LOGE("GenerateSignature: OEMCrypto_GenerateSignature err=%d", sts);
|
|
return false;
|
|
}
|
|
|
|
bool CryptoSession::GenerateRsaSignature(const std::string& message,
|
|
std::string* signature) {
|
|
LOGV("GenerateRsaSignature: id=%ld", (uint32_t)oec_session_id_);
|
|
if (!signature) {
|
|
LOGE("GenerateRsaSignature: null signature string");
|
|
return false;
|
|
}
|
|
|
|
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) {
|
|
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 true;
|
|
}
|
|
if (OEMCrypto_ERROR_SHORT_BUFFER != sts) {
|
|
break;
|
|
}
|
|
|
|
// Retry with proper-sized signature buffer
|
|
signature->resize(length);
|
|
}
|
|
|
|
LOGE("GenerateRsaSignature: OEMCrypto_GenerateRSASignature err=%d", sts);
|
|
return false;
|
|
}
|
|
|
|
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)) {
|
|
|
|
M_TIME(
|
|
sts = OEMCrypto_CopyBuffer(
|
|
requested_security_level_,
|
|
params.encrypt_buffer,
|
|
params.encrypt_length,
|
|
&buffer_descriptor,
|
|
params.subsample_flags),
|
|
metrics_,
|
|
oemcrypto_copy_buffer_,
|
|
sts,
|
|
requested_security_level_,
|
|
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 (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
|
|
AutoLock auto_lock(crypto_lock_);
|
|
// Check if key needs to be selected
|
|
if (params.is_encrypted) {
|
|
CdmResponseType result = SelectKey(*params.key_id);
|
|
if (result != NO_ERROR) return result;
|
|
}
|
|
|
|
M_TIME(
|
|
sts = OEMCrypto_DecryptCENC(
|
|
oec_session_id_,
|
|
params.encrypt_buffer,
|
|
params.encrypt_length,
|
|
params.is_encrypted,
|
|
&(*params.iv).front(),
|
|
params.block_offset,
|
|
&buffer_descriptor,
|
|
&pattern_descriptor,
|
|
params.subsample_flags),
|
|
metrics_,
|
|
oemcrypto_decrypt_cenc_,
|
|
sts,
|
|
metrics::Pow2Bucket(params.encrypt_length));
|
|
|
|
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;
|
|
case OEMCrypto_ERROR_KEY_EXPIRED:
|
|
return NEED_KEY;
|
|
case OEMCrypto_ERROR_INVALID_SESSION:
|
|
return SESSION_NOT_FOUND_FOR_DECRYPT;
|
|
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;
|
|
default:
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
}
|
|
|
|
bool CryptoSession::UsageInformationSupport(bool* has_support) {
|
|
LOGV("UsageInformationSupport: id=%ld", (uint32_t)oec_session_id_);
|
|
if (!initialized_) return false;
|
|
|
|
*has_support = OEMCrypto_SupportsUsageTable(requested_security_level_);
|
|
return true;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::UpdateUsageInformation() {
|
|
LOGV("UpdateUsageInformation: id=%ld", (uint32_t)oec_session_id_);
|
|
AutoLock auto_lock(crypto_lock_);
|
|
if (!initialized_) return UNKNOWN_ERROR;
|
|
|
|
if (usage_table_header_ != NULL) {
|
|
LOGV("UpdateUsageInformation: deprecated for OEMCrypto v13+");
|
|
return NO_ERROR;
|
|
}
|
|
|
|
OEMCryptoResult status;
|
|
M_TIME(
|
|
status = OEMCrypto_UpdateUsageTable(),
|
|
metrics_,
|
|
oemcrypto_update_usage_table_,
|
|
status);
|
|
if (status != OEMCrypto_SUCCESS) {
|
|
LOGE("CryptoSession::UsageUsageInformation: error=%ld", status);
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::DeactivateUsageInformation(
|
|
const std::string& provider_session_token) {
|
|
LOGV("DeactivateUsageInformation: id=%ld", (uint32_t)oec_session_id_);
|
|
|
|
AutoLock auto_lock(crypto_lock_);
|
|
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;
|
|
M_TIME(
|
|
status = OEMCrypto_DeactivateUsageEntry(
|
|
(uint32_t)oec_session_id_,
|
|
pst,
|
|
provider_session_token.length()),
|
|
metrics_,
|
|
oemcrypto_deactivate_usage_entry_,
|
|
status);
|
|
|
|
switch (status) {
|
|
case OEMCrypto_SUCCESS:
|
|
return NO_ERROR;
|
|
case OEMCrypto_ERROR_INVALID_CONTEXT:
|
|
LOGE("CryptoSession::DeactivateUsageInformation: invalid context error");
|
|
return KEY_CANCELED;
|
|
default:
|
|
LOGE("CryptoSession::DeactivateUsageInformation: error=%ld", status);
|
|
return UNKNOWN_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("GenerateUsageReport: id=%ld", (uint32_t)oec_session_id_);
|
|
|
|
if (NULL == usage_report) {
|
|
LOGE("CryptoSession::GenerateUsageReport: usage_report parameter is null");
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
|
|
AutoLock auto_lock(crypto_lock_);
|
|
uint8_t* pst = reinterpret_cast<uint8_t*>(
|
|
const_cast<char*>(provider_session_token.data()));
|
|
|
|
size_t usage_length = 0;
|
|
OEMCryptoResult status;
|
|
M_TIME(
|
|
status = OEMCrypto_ReportUsage(
|
|
oec_session_id_,
|
|
pst,
|
|
provider_session_token.length(),
|
|
NULL,
|
|
&usage_length),
|
|
metrics_,
|
|
oemcrypto_report_usage_,
|
|
status);
|
|
|
|
if (OEMCrypto_SUCCESS != status) {
|
|
if (OEMCrypto_ERROR_SHORT_BUFFER != status) {
|
|
LOGE("CryptoSession::GenerateUsageReport: Report Usage error=%ld",
|
|
status);
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
}
|
|
|
|
std::vector<uint8_t> buffer(usage_length);
|
|
|
|
M_TIME(
|
|
status = OEMCrypto_ReportUsage(
|
|
oec_session_id_,
|
|
pst,
|
|
provider_session_token.length(),
|
|
&buffer[0],
|
|
&usage_length),
|
|
metrics_,
|
|
oemcrypto_report_usage_,
|
|
status);
|
|
|
|
if (OEMCrypto_SUCCESS != status) {
|
|
LOGE("CryptoSession::GenerateUsageReport: Report Usage error=%ld", status);
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
|
|
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("CryptoSession::GenerateUsageReport: usage report too small=%ld",
|
|
usage_length);
|
|
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", pst_report.status());
|
|
LOGV("OEMCrypto_PST_Report.clock_security_level: %d\n",
|
|
pst_report.clock_security_level());
|
|
LOGV("OEMCrypto_PST_Report.pst_length: %d\n",
|
|
pst_report.pst_length());
|
|
LOGV("OEMCrypto_PST_Report.padding: %d\n", pst_report.padding());
|
|
LOGV("OEMCrypto_PST_Report.seconds_since_license_received: %lld\n",
|
|
pst_report.seconds_since_license_received());
|
|
LOGV("OEMCrypto_PST_Report.seconds_since_first_decrypt: %lld\n",
|
|
pst_report.seconds_since_first_decrypt());
|
|
LOGV("OEMCrypto_PST_Report.seconds_since_last_decrypt: %lld\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("ReleaseUsageInformation: id=%ld", (uint32_t)oec_session_id_);
|
|
{
|
|
AutoLock auto_lock(crypto_lock_);
|
|
if (usage_table_header_ != NULL) {
|
|
LOGW("ReleaseUsageInformation: 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;
|
|
M_TIME(
|
|
status = OEMCrypto_DeleteUsageEntry(
|
|
oec_session_id_,
|
|
pst,
|
|
provider_session_token.length(),
|
|
msg,
|
|
message.length(),
|
|
sig,
|
|
signature.length()),
|
|
metrics_,
|
|
oemcrypto_delete_usage_entry_,
|
|
status);
|
|
|
|
if (OEMCrypto_SUCCESS != status) {
|
|
LOGE("CryptoSession::ReleaseUsageInformation: Report Usage error=%ld",
|
|
status);
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
}
|
|
|
|
UpdateUsageInformation();
|
|
return NO_ERROR;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::DeleteUsageInformation(
|
|
const std::string& provider_session_token) {
|
|
CdmResponseType response = NO_ERROR;
|
|
LOGV("CryptoSession::DeleteUsageInformation");
|
|
OEMCryptoResult status;
|
|
{
|
|
AutoLock auto_lock(crypto_lock_);
|
|
M_TIME(
|
|
status = OEMCrypto_ForceDeleteUsageEntry(
|
|
reinterpret_cast<const uint8_t*>(provider_session_token.c_str()),
|
|
provider_session_token.length()),
|
|
metrics_,
|
|
oemcrypto_force_delete_usage_entry_,
|
|
status);
|
|
if (OEMCrypto_SUCCESS != status) {
|
|
LOGE("CryptoSession::DeleteUsageInformation: Delete Usage Table error "
|
|
"= %ld", status);
|
|
response = UNKNOWN_ERROR;
|
|
}
|
|
}
|
|
UpdateUsageInformation();
|
|
return response;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::DeleteMultipleUsageInformation(
|
|
const std::vector<std::string>& provider_session_tokens) {
|
|
LOGV("CryptoSession::DeleteMultipleUsageInformation");
|
|
CdmResponseType response = NO_ERROR;
|
|
{
|
|
AutoLock auto_lock(crypto_lock_);
|
|
for (size_t i=0; i < provider_session_tokens.size(); ++i) {
|
|
OEMCryptoResult status;
|
|
M_TIME(
|
|
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_,
|
|
status);
|
|
if (OEMCrypto_SUCCESS != status) {
|
|
LOGW("CryptoSession::DeleteMultipleUsageInformation: "
|
|
"Delete Usage Table error =%ld", status);
|
|
response = UNKNOWN_ERROR;
|
|
}
|
|
}
|
|
}
|
|
UpdateUsageInformation();
|
|
return response;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::DeleteAllUsageReports() {
|
|
LOGV("DeleteAllUsageReports");
|
|
OEMCryptoResult status;
|
|
{
|
|
AutoLock auto_lock(crypto_lock_);
|
|
M_TIME(
|
|
status = OEMCrypto_DeleteOldUsageTable(),
|
|
metrics_,
|
|
oemcrypto_delete_usage_table_,
|
|
status);
|
|
if (OEMCrypto_SUCCESS != status) {
|
|
LOGE("CryptoSession::DeleteAllUsageReports: Delete Usage Table error "
|
|
"=%ld", status);
|
|
}
|
|
}
|
|
|
|
UpdateUsageInformation();
|
|
return NO_ERROR;
|
|
}
|
|
|
|
bool CryptoSession::IsAntiRollbackHwPresent() {
|
|
bool is_present;
|
|
M_TIME(
|
|
is_present = OEMCrypto_IsAntiRollbackHwPresent(requested_security_level_),
|
|
metrics_,
|
|
oemcrypto_is_anti_rollback_hw_present_,
|
|
is_present,
|
|
requested_security_level_);
|
|
return is_present;
|
|
}
|
|
|
|
bool CryptoSession::GenerateNonce(uint32_t* nonce) {
|
|
if (!nonce) {
|
|
LOGE("input parameter is null");
|
|
return false;
|
|
}
|
|
|
|
LOGV("CryptoSession::GenerateNonce: Lock");
|
|
AutoLock auto_lock(crypto_lock_);
|
|
|
|
OEMCryptoResult result;
|
|
M_TIME(
|
|
result = OEMCrypto_GenerateNonce(
|
|
oec_session_id_,
|
|
nonce),
|
|
metrics_,
|
|
oemcrypto_generate_nonce_,
|
|
result);
|
|
return OEMCrypto_SUCCESS == result;
|
|
}
|
|
|
|
bool CryptoSession::SetDestinationBufferType() {
|
|
if (Properties::oem_crypto_use_secure_buffers()) {
|
|
if (GetSecurityLevel() == kSecurityLevelL1) {
|
|
destination_buffer_type_ = OEMCrypto_BufferType_Secure;
|
|
} else {
|
|
destination_buffer_type_ = OEMCrypto_BufferType_Clear;
|
|
}
|
|
} 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;
|
|
}
|
|
|
|
bool 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("CryptoSession::RewrapCertificate, session id=%ld",
|
|
static_cast<uint32_t>(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("CryptoSession::RewrapCertificate, Bad pre-provision type=%d: "
|
|
"session id=%ld", pre_provision_token_type_,
|
|
static_cast<uint32_t>(oec_session_id_));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool 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("CryptoSession::RewrapDeviceRSAKey, session id=%ld",
|
|
static_cast<uint32_t>(oec_session_id_));
|
|
|
|
const uint8_t* signed_msg = reinterpret_cast<const uint8_t*>(message.data());
|
|
const uint8_t* msg_rsa_key = NULL;
|
|
const uint8_t* msg_rsa_key_iv = NULL;
|
|
const uint32_t* msg_nonce = NULL;
|
|
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 NULL as uint8_t* wrapped_rsa_key
|
|
// and 0 as wrapped_rsa_key_length.
|
|
size_t wrapped_rsa_key_length = 0;
|
|
OEMCryptoResult status;
|
|
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,
|
|
NULL,
|
|
&wrapped_rsa_key_length),
|
|
metrics_,
|
|
oemcrypto_rewrap_device_rsa_key_,
|
|
status);
|
|
if (status != OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
LOGE("OEMCrypto_RewrapDeviceRSAKey fails to get wrapped_rsa_key_length");
|
|
return false;
|
|
}
|
|
|
|
wrapped_rsa_key->resize(wrapped_rsa_key_length);
|
|
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);
|
|
|
|
if (OEMCrypto_SUCCESS != status) {
|
|
LOGE("OEMCrypto_RewrapDeviceRSAKey fails with %d", status);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool 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("CryptoSession::RewrapDeviceRSAKey30, session id=%ld",
|
|
static_cast<uint32_t>(oec_session_id_));
|
|
|
|
const uint8_t* signed_msg = reinterpret_cast<const uint8_t*>(message.data());
|
|
const uint8_t* msg_private_key = NULL;
|
|
const uint8_t* msg_iv = NULL;
|
|
const uint32_t* msg_nonce = NULL;
|
|
const uint8_t* msg_wrapping_key = NULL;
|
|
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 NULL as uint8_t* wrapped_rsa_key
|
|
// and 0 as wrapped_rsa_key_length.
|
|
size_t wrapped_private_key_length = 0;
|
|
OEMCryptoResult status = OEMCrypto_RewrapDeviceRSAKey30(
|
|
oec_session_id_, msg_nonce, msg_wrapping_key, wrapping_key.size(),
|
|
msg_private_key, private_key.size(), msg_iv, NULL,
|
|
&wrapped_private_key_length);
|
|
|
|
if (status != OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
LOGE("OEMCrypto_RewrapDeviceRSAKey30 failed getting wrapped key length");
|
|
return false;
|
|
}
|
|
|
|
wrapped_private_key->resize(wrapped_private_key_length);
|
|
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);
|
|
|
|
wrapped_private_key->resize(wrapped_private_key_length);
|
|
|
|
if (OEMCrypto_SUCCESS != status) {
|
|
LOGE("OEMCrypto_RewrapDeviceRSAKey fails with %d", status);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool CryptoSession::GetHdcpCapabilities(HdcpCapability* current,
|
|
HdcpCapability* max) {
|
|
LOGV("GetHdcpCapabilities: id=%ld", (uint32_t)oec_session_id_);
|
|
if (!initialized_) return false;
|
|
if (current == NULL || max == NULL) {
|
|
LOGE("CryptoSession::GetHdcpCapabilities: |current|, |max| cannot be NULL");
|
|
return false;
|
|
}
|
|
OEMCryptoResult status;
|
|
M_TIME(
|
|
status = OEMCrypto_GetHDCPCapability(
|
|
requested_security_level_,
|
|
current,
|
|
max),
|
|
metrics_,
|
|
oemcrypto_get_hdcp_capability_,
|
|
status,
|
|
requested_security_level_);
|
|
if (OEMCrypto_SUCCESS != status) {
|
|
LOGW("OEMCrypto_GetHDCPCapability fails with %d", status);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool CryptoSession::GetSupportedCertificateTypes(
|
|
SupportedCertificateTypes* support) {
|
|
LOGV("GetSupportedCertificateTypes: id=%ld", (uint32_t)oec_session_id_);
|
|
if (!initialized_) return false;
|
|
if (support == NULL) {
|
|
LOGE("CryptoSession::GetSupportedCertificateTypes: |support| cannot be "
|
|
"NULL");
|
|
return false;
|
|
}
|
|
|
|
uint32_t 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;
|
|
}
|
|
|
|
bool CryptoSession::GetRandom(size_t data_length, uint8_t* random_data) {
|
|
if (random_data == NULL) {
|
|
LOGE("CryptoSession::GetRandom: random data destination not provided");
|
|
return false;
|
|
}
|
|
OEMCryptoResult sts;
|
|
M_TIME(
|
|
sts = OEMCrypto_GetRandom(
|
|
random_data,
|
|
data_length),
|
|
metrics_,
|
|
oemcrypto_get_random_,
|
|
sts,
|
|
metrics::Pow2Bucket(data_length));
|
|
|
|
if (sts != OEMCrypto_SUCCESS) {
|
|
LOGE("OEMCrypto_GetRandom fails with %d", sts);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CryptoSession::GetNumberOfOpenSessions(size_t* count) {
|
|
LOGV("GetNumberOfOpenSessions");
|
|
if (!initialized_) return false;
|
|
if (count == NULL) {
|
|
LOGE("CryptoSession::GetNumberOfOpenSessions: |count| cannot be NULL");
|
|
return false;
|
|
}
|
|
|
|
size_t sessions_count;
|
|
OEMCryptoResult status;
|
|
M_TIME(
|
|
status = OEMCrypto_GetNumberOfOpenSessions(
|
|
requested_security_level_,
|
|
&sessions_count),
|
|
metrics_,
|
|
oemcrypto_get_number_of_open_sessions_,
|
|
status,
|
|
requested_security_level_);
|
|
if (OEMCrypto_SUCCESS != status) {
|
|
LOGW("OEMCrypto_GetNumberOfOpenSessions fails with %d", status);
|
|
return false;
|
|
}
|
|
*count = sessions_count;
|
|
return true;
|
|
}
|
|
|
|
bool CryptoSession::GetMaxNumberOfSessions(size_t* max) {
|
|
LOGV("GetMaxNumberOfSessions");
|
|
if (!initialized_) return false;
|
|
if (max == NULL) {
|
|
LOGE("CryptoSession::GetMaxNumberOfSessions: |max| cannot be NULL");
|
|
return false;
|
|
}
|
|
|
|
size_t max_sessions;
|
|
OEMCryptoResult status;
|
|
M_TIME(
|
|
status = OEMCrypto_GetMaxNumberOfSessions(
|
|
requested_security_level_,
|
|
&max_sessions),
|
|
metrics_,
|
|
oemcrypto_get_max_number_of_sessions_,
|
|
status,
|
|
requested_security_level_);
|
|
|
|
if (OEMCrypto_SUCCESS != status) {
|
|
LOGW("OEMCrypto_GetMaxNumberOfSessions fails with %d", status);
|
|
return false;
|
|
}
|
|
*max = max_sessions;
|
|
return true;
|
|
}
|
|
|
|
bool CryptoSession::GetSrmVersion(uint16_t* srm_version) {
|
|
LOGV("GetSrmVersion");
|
|
if (!initialized_) return false;
|
|
if (srm_version == NULL) {
|
|
LOGE("CryptoSession::GetSrmVersion: |srm_version| cannot be NULL");
|
|
return false;
|
|
}
|
|
|
|
OEMCryptoResult status = OEMCrypto_GetCurrentSRMVersion(srm_version);
|
|
switch (status) {
|
|
case OEMCrypto_SUCCESS:
|
|
return true;
|
|
case OEMCrypto_ERROR_NOT_IMPLEMENTED:
|
|
return false;
|
|
default:
|
|
LOGW("OEMCrypto_GetCurrentSRMVersion fails with %d", status);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool CryptoSession::IsSrmUpdateSupported() {
|
|
LOGV("IsSrmUpdateSupported");
|
|
if (!initialized_) return false;
|
|
return OEMCrypto_IsSRMUpdateSupported();
|
|
}
|
|
|
|
bool CryptoSession::LoadSrm(const std::string& srm) {
|
|
LOGV("LoadSrm");
|
|
if (!initialized_) return false;
|
|
if (srm.empty()) {
|
|
LOGE("CryptoSession::LoadSrm: |srm| cannot be empty");
|
|
return false;
|
|
}
|
|
|
|
OEMCryptoResult status = OEMCrypto_LoadSRM(
|
|
reinterpret_cast<const uint8_t*>(srm.data()), srm.size());
|
|
if (OEMCrypto_SUCCESS != status) {
|
|
LOGW("OEMCrypto_LoadSRM fails with %d", status);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GenericEncrypt(const std::string& in_buffer,
|
|
const std::string& key_id,
|
|
const std::string& iv,
|
|
CdmEncryptionAlgorithm algorithm,
|
|
std::string* out_buffer) {
|
|
LOGV("GenericEncrypt: id=%ld", (uint32_t)oec_session_id_);
|
|
if (!out_buffer) return INVALID_PARAMETERS_ENG_9;
|
|
|
|
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());
|
|
}
|
|
|
|
AutoLock auto_lock(crypto_lock_);
|
|
CdmResponseType result = SelectKey(key_id);
|
|
if (result != NO_ERROR) return result;
|
|
|
|
OEMCryptoResult sts;
|
|
|
|
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("GenericEncrypt: OEMCrypto_Generic_Encrypt err=%d", sts);
|
|
if (OEMCrypto_ERROR_KEY_EXPIRED == sts) {
|
|
return NEED_KEY;
|
|
} else if (OEMCrypto_ERROR_NO_CONTENT_KEY == sts) {
|
|
return KEY_NOT_FOUND_3;
|
|
} else {
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
}
|
|
return NO_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("GenericDecrypt: id=%ld", (uint32_t)oec_session_id_);
|
|
if (!out_buffer) return INVALID_PARAMETERS_ENG_10;
|
|
|
|
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());
|
|
}
|
|
|
|
AutoLock auto_lock(crypto_lock_);
|
|
CdmResponseType result = SelectKey(key_id);
|
|
if (result != NO_ERROR) return result;
|
|
|
|
OEMCryptoResult sts;
|
|
|
|
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("GenericDecrypt: OEMCrypto_Generic_Decrypt err=%d", sts);
|
|
if (OEMCrypto_ERROR_KEY_EXPIRED == sts) {
|
|
return NEED_KEY;
|
|
} else if (OEMCrypto_ERROR_NO_CONTENT_KEY == sts) {
|
|
return KEY_NOT_FOUND_4;
|
|
} else {
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GenericSign(const std::string& message,
|
|
const std::string& key_id,
|
|
CdmSigningAlgorithm algorithm,
|
|
std::string* signature) {
|
|
LOGV("GenericSign: id=%ld", (uint32_t)oec_session_id_);
|
|
if (!signature) {
|
|
LOGE("GenerateSign: null signature string");
|
|
return INVALID_PARAMETERS_ENG_11;
|
|
}
|
|
|
|
OEMCrypto_Algorithm oec_algorithm = GenericSigningAlgorithm(algorithm);
|
|
if (oec_algorithm == kInvalidAlgorithm) {
|
|
return INVALID_PARAMETERS_ENG_15;
|
|
}
|
|
|
|
OEMCryptoResult sts;
|
|
size_t length = signature->size();
|
|
|
|
AutoLock auto_lock(crypto_lock_);
|
|
CdmResponseType result = SelectKey(key_id);
|
|
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) {
|
|
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("GenericSign: OEMCrypto_Generic_Sign err=%d", sts);
|
|
if (OEMCrypto_ERROR_KEY_EXPIRED == sts) {
|
|
return NEED_KEY;
|
|
} else if (OEMCrypto_ERROR_NO_CONTENT_KEY == sts) {
|
|
return KEY_NOT_FOUND_5;
|
|
} else {
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GenericVerify(const std::string& message,
|
|
const std::string& key_id,
|
|
CdmSigningAlgorithm algorithm,
|
|
const std::string& signature) {
|
|
LOGV("GenericVerify: id=%ld", (uint32_t)oec_session_id_);
|
|
|
|
OEMCrypto_Algorithm oec_algorithm = GenericSigningAlgorithm(algorithm);
|
|
if (oec_algorithm == kInvalidAlgorithm) {
|
|
return INVALID_PARAMETERS_ENG_16;
|
|
}
|
|
|
|
AutoLock auto_lock(crypto_lock_);
|
|
CdmResponseType result = SelectKey(key_id);
|
|
if (result != NO_ERROR) return result;
|
|
|
|
OEMCryptoResult sts;
|
|
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("GenericVerify: OEMCrypto_Generic_Verify err=%d", sts);
|
|
if (OEMCrypto_ERROR_KEY_EXPIRED == sts) {
|
|
return NEED_KEY;
|
|
} else if (OEMCrypto_ERROR_NO_CONTENT_KEY == sts) {
|
|
return KEY_NOT_FOUND_6;
|
|
} else {
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::GetUsageSupportType(
|
|
CdmUsageSupportType* usage_support_type) {
|
|
LOGV("GetUsageSupportType: id=%ld", (uint32_t)oec_session_id_);
|
|
|
|
if (usage_support_type == NULL) {
|
|
LOGE("GetUsageSupportType: usage_support_type param not provided");
|
|
return INVALID_PARAMETERS_ENG_23;
|
|
}
|
|
|
|
if (is_usage_support_type_valid_) {
|
|
*usage_support_type = usage_support_type_;
|
|
return NO_ERROR;
|
|
}
|
|
|
|
bool has_support = false;
|
|
if (!UsageInformationSupport(&has_support)) {
|
|
LOGE("GetUsageSupportType: UsageInformationSupport failed");
|
|
return USAGE_INFORMATION_SUPPORT_FAILED;
|
|
}
|
|
|
|
if (!has_support) {
|
|
*usage_support_type = usage_support_type_ = kNonSecureUsageSupport;
|
|
is_usage_support_type_valid_ = true;
|
|
return NO_ERROR;
|
|
}
|
|
|
|
uint32_t api_version = 0;
|
|
if (!GetApiVersion(&api_version)) {
|
|
LOGE("GetUsageSupportType: GetApiVersion failed");
|
|
return USAGE_SUPPORT_GET_API_FAILED;
|
|
}
|
|
|
|
*usage_support_type = usage_support_type_ =
|
|
(api_version >= kOemCryptoApiVersionSupportsBigUsageTables) ?
|
|
kUsageEntrySupport : kUsageTableSupport;
|
|
is_usage_support_type_valid_ = true;
|
|
return NO_ERROR;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::CreateUsageTableHeader(
|
|
CdmUsageTableHeader* usage_table_header) {
|
|
LOGV("CreateUsageTableHeader: id=%ld", (uint32_t)oec_session_id_);
|
|
|
|
if (usage_table_header == NULL) {
|
|
LOGE("CreateUsageTableHeader: usage_table_header param not provided");
|
|
return INVALID_PARAMETERS_ENG_17;
|
|
}
|
|
|
|
usage_table_header->resize(kEstimatedInitialUsageTableHeader);
|
|
|
|
size_t usage_table_header_size = usage_table_header->size();
|
|
OEMCryptoResult result = OEMCrypto_CreateUsageTableHeader(
|
|
requested_security_level_,
|
|
reinterpret_cast<uint8_t*>(const_cast<char*>(usage_table_header->data())),
|
|
&usage_table_header_size);
|
|
|
|
if (result == OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
usage_table_header->resize(usage_table_header_size);
|
|
result = OEMCrypto_CreateUsageTableHeader(
|
|
requested_security_level_,
|
|
reinterpret_cast<uint8_t*>(
|
|
const_cast<char*>(usage_table_header->data())),
|
|
&usage_table_header_size);
|
|
}
|
|
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("CreateUsageTableHeader: usage table header creation failed: %d",
|
|
result);
|
|
return CREATE_USAGE_TABLE_ERROR;
|
|
}
|
|
|
|
usage_table_header->resize(usage_table_header_size);
|
|
return NO_ERROR;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::LoadUsageTableHeader(
|
|
const CdmUsageTableHeader& usage_table_header) {
|
|
LOGV("LoadUsageTableHeader: id=%ld", (uint32_t)oec_session_id_);
|
|
|
|
OEMCryptoResult result = OEMCrypto_LoadUsageTableHeader(
|
|
requested_security_level_,
|
|
reinterpret_cast<const uint8_t*>(usage_table_header.data()),
|
|
usage_table_header.size());
|
|
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
if (result == OEMCrypto_WARNING_GENERATION_SKEW) {
|
|
LOGW(
|
|
"LoadUsageTableHeader: OEMCrypto_LoadUsageTableHeader warning: "
|
|
"generation skew");
|
|
} else {
|
|
LOGE("LoadUsageTableHeader: OEMCrypto_LoadUsageTableHeader error: %d",
|
|
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_UNKNOWN_FAILURE:
|
|
default:
|
|
return LOAD_USAGE_HEADER_UNKNOWN_ERROR;
|
|
}
|
|
}
|
|
|
|
CdmResponseType CryptoSession::CreateUsageEntry(uint32_t* entry_number) {
|
|
LOGV("CreateUsageEntry: id=%ld", (uint32_t)oec_session_id_);
|
|
|
|
if (entry_number == NULL) {
|
|
LOGE("CreateUsageEntry: entry_number param not provided");
|
|
return INVALID_PARAMETERS_ENG_18;
|
|
}
|
|
|
|
OEMCryptoResult result =
|
|
OEMCrypto_CreateNewUsageEntry(oec_session_id_, entry_number);
|
|
|
|
switch (result) {
|
|
case OEMCrypto_SUCCESS:
|
|
return NO_ERROR;
|
|
case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES:
|
|
LOGE(
|
|
"CreateUsageEntry: OEMCrypto_CreateNewUsageEntry error: "
|
|
"Insufficient resources");
|
|
return INSUFFICIENT_CRYPTO_RESOURCES_3;
|
|
default:
|
|
LOGE("CreateUsageEntry: OEMCrypto_CreateNewUsageEntry error: %d", result);
|
|
return CREATE_USAGE_ENTRY_UNKNOWN_ERROR;
|
|
}
|
|
}
|
|
|
|
CdmResponseType CryptoSession::LoadUsageEntry(
|
|
uint32_t entry_number,
|
|
const CdmUsageEntry& usage_entry) {
|
|
LOGV("LoadUsageEntry: id=%ld", (uint32_t)oec_session_id_);
|
|
|
|
OEMCryptoResult result = OEMCrypto_LoadUsageEntry(
|
|
oec_session_id_, entry_number,
|
|
reinterpret_cast<const uint8_t*>(usage_entry.data()), usage_entry.size());
|
|
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
if (result == OEMCrypto_WARNING_GENERATION_SKEW) {
|
|
LOGW("LoadUsageEntry: OEMCrypto_LoadUsageEntry warning: generation skew");
|
|
} else {
|
|
LOGE("LoadUsageTableHeader: OEMCrypto_LoadUsageEntry error: %d", 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;
|
|
default:
|
|
return LOAD_USAGE_ENTRY_UNKNOWN_ERROR;
|
|
}
|
|
}
|
|
|
|
CdmResponseType CryptoSession::UpdateUsageEntry(
|
|
CdmUsageTableHeader* usage_table_header,
|
|
CdmUsageEntry* usage_entry) {
|
|
LOGV("UpdateUsageEntry: id=%ld", (uint32_t)oec_session_id_);
|
|
|
|
if (usage_table_header == NULL) {
|
|
LOGE("UpdateUsageEntry: usage_table_header param not provided");
|
|
return INVALID_PARAMETERS_ENG_19;
|
|
}
|
|
|
|
if (usage_entry == NULL) {
|
|
LOGE("UpdateUsageEntry: usage_entry param not provided");
|
|
return INVALID_PARAMETERS_ENG_20;
|
|
}
|
|
|
|
size_t usage_table_header_len = 0;
|
|
size_t usage_entry_len = 0;
|
|
OEMCryptoResult result = OEMCrypto_UpdateUsageEntry(
|
|
oec_session_id_, NULL, &usage_table_header_len, NULL, &usage_entry_len);
|
|
|
|
if (result == OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
usage_table_header->resize(usage_table_header_len);
|
|
usage_entry->resize(usage_entry_len);
|
|
|
|
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);
|
|
}
|
|
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("UpdateUsageEntry: OEMCrypto_UpdateUsageEntry error: %d", result);
|
|
return UPDATE_USAGE_ENTRY_UNKNOWN_ERROR;
|
|
}
|
|
|
|
usage_table_header->resize(usage_table_header_len);
|
|
usage_entry->resize(usage_entry_len);
|
|
return NO_ERROR;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::ShrinkUsageTableHeader(
|
|
uint32_t new_entry_count, CdmUsageTableHeader* usage_table_header) {
|
|
LOGV("ShrinkUsageTableHeader: id=%ld", (uint32_t)oec_session_id_);
|
|
|
|
if (usage_table_header == NULL) {
|
|
LOGE(
|
|
"ShrinkUsageTableHeader: usage_table_header param not provided");
|
|
return INVALID_PARAMETERS_ENG_21;
|
|
}
|
|
|
|
size_t usage_table_header_len = 0;
|
|
OEMCryptoResult result = OEMCrypto_ShrinkUsageTableHeader(
|
|
requested_security_level_, new_entry_count, NULL,
|
|
&usage_table_header_len);
|
|
|
|
if (result == OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
usage_table_header->resize(usage_table_header_len);
|
|
|
|
result = OEMCrypto_ShrinkUsageTableHeader(
|
|
requested_security_level_, new_entry_count,
|
|
reinterpret_cast<uint8_t*>(
|
|
const_cast<char*>(usage_table_header->data())),
|
|
&usage_table_header_len);
|
|
}
|
|
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE(
|
|
"ShrinkUsageTableHeader: OEMCrypto_ShrinkUsageTableHeader error: %d",
|
|
result);
|
|
return SHRINK_USAGE_TABLER_HEADER_UNKNOWN_ERROR;
|
|
}
|
|
|
|
usage_table_header->resize(usage_table_header_len);
|
|
return NO_ERROR;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::MoveUsageEntry(uint32_t new_entry_number) {
|
|
LOGV("MoveUsageEntry: id=%ld", (uint32_t)oec_session_id_);
|
|
|
|
OEMCryptoResult result =
|
|
OEMCrypto_MoveEntry(oec_session_id_, new_entry_number);
|
|
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("MoveUsageEntry: OEMCrypto_MoveEntry error: %d", result);
|
|
return MOVE_USAGE_ENTRY_UNKNOWN_ERROR;
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
CdmResponseType CryptoSession::CopyOldUsageEntry(
|
|
const std::string& provider_session_token) {
|
|
LOGV("CopyOldUsageEntry: id=%ld", (uint32_t)oec_session_id_);
|
|
|
|
OEMCryptoResult result = OEMCrypto_CopyOldUsageEntry(
|
|
oec_session_id_,
|
|
reinterpret_cast<const uint8_t*>(provider_session_token.data()),
|
|
provider_session_token.size());
|
|
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("CopyOldUsageEntry: OEMCrypto_CopyOldUsageEntry error: %d", result);
|
|
return COPY_OLD_USAGE_ENTRY_UNKNOWN_ERROR;
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
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 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.
|
|
switch (buffer_descriptor.type) {
|
|
case OEMCrypto_BufferType_Clear:
|
|
buffer_descriptor.buffer.clear.address =
|
|
static_cast<uint8_t*>(params.decrypt_buffer) +
|
|
params.decrypt_buffer_offset + additional_offset;
|
|
buffer_descriptor.buffer.clear.max_length =
|
|
params.decrypt_buffer_length -
|
|
(params.decrypt_buffer_offset + additional_offset);
|
|
break;
|
|
case OEMCrypto_BufferType_Secure:
|
|
buffer_descriptor.buffer.secure.offset =
|
|
params.decrypt_buffer_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;
|
|
M_TIME(
|
|
sts = OEMCrypto_CopyBuffer(
|
|
requested_security_level_,
|
|
params.encrypt_buffer + additional_offset,
|
|
chunk_size,
|
|
&buffer_descriptor,
|
|
subsample_flags),
|
|
metrics_,
|
|
oemcrypto_copy_buffer_,
|
|
sts,
|
|
requested_security_level_,
|
|
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;
|
|
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 = NULL;
|
|
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);
|
|
}
|
|
|
|
} // namespace wvcdm
|