Files
android/libwvdrmengine/cdm/core/src/cdm_engine.cpp
Alex Dale 5eed0446da Clean up CdmEngine logs.
[ Merge of http://go/wvgerrit/121568 ]

The CdmEngine logs had both too much and too little information.

Since our logging has been enabled to print function names natively,
many of the log information has become superfluous.  Needless
information has been removed, and many of the important INFO logs have
been reduced to only the information not present in the function name.

Some of the INFO and ERROR logs were missing identifiers to match
failures with the same session request should the failures take more
than a few milliseconds to occur.  CDM session IDs and key set IDs
have been included in all the logs that _appeared_ to have a slow
operation between the top of the method and log.

To help make enum values more readable, several enums-to-string
functions have been implemented.  These converters are intended for
INFO logging and as such, do not log any addition information should
the enum be out of range.

To help make empty and null identifiers more readable in the logs,
empty strings will be logged as <empty> and null strings will be
logged as <null>.

While working through the "cdm_engine.cpp" file, a few minor changes
have been made:
- Adjust if statements to match with Google C++ style guidelines
  - Skipped anything that was not obvious
- Added a const qualifier to variables where appropriate
- Moved some null checks to the top of the method
  - Only where sequence is non-critical to normal operation
- Removed unnecessary string to vector to string conversions
- Reject empty |force_session_id|
  - Already enforced on CE CDM code and not uesd on Android

Bug: 183576879
Test: CE CDM unittests
Change-Id: Id165373055f7ce6097c93c48f84af74bd353c8cb
2021-04-21 21:05:10 -07:00

1955 lines
68 KiB
C++

// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine License
// Agreement.
#include "cdm_engine.h"
#include <assert.h>
#include <stdlib.h>
#include <iostream>
#include <limits>
#include <list>
#include <memory>
#include <sstream>
#include <utility>
#include "cdm_random.h"
#include "cdm_session.h"
#include "cdm_session_map.h"
#include "clock.h"
#include "device_files.h"
#include "file_store.h"
#include "log.h"
#include "properties.h"
#include "string_conversions.h"
#include "wv_cdm_constants.h"
#include "wv_cdm_event_listener.h"
namespace wvcdm {
namespace {
const uint64_t kReleaseSessionTimeToLive = 60; // seconds
const uint32_t kUpdateUsageInformationPeriod = 60; // seconds
wvcdm::CdmOfflineLicenseState MapDeviceFilesLicenseState(
wvcdm::DeviceFiles::LicenseState state) {
switch (state) {
case wvcdm::DeviceFiles::LicenseState::kLicenseStateActive:
return wvcdm::kLicenseStateActive;
case wvcdm::DeviceFiles::LicenseState::kLicenseStateReleasing:
return wvcdm::kLicenseStateReleasing;
default:
return wvcdm::kLicenseStateUnknown;
}
}
} // namespace
class UsagePropertySet : public CdmClientPropertySet {
public:
UsagePropertySet() {}
~UsagePropertySet() override {}
void set_security_level(SecurityLevel security_level) {
if (kLevel3 == security_level)
security_level_ = QUERY_VALUE_SECURITY_LEVEL_L3;
else
security_level_.clear();
}
const std::string& security_level() const override { return security_level_; }
bool use_privacy_mode() const override { return false; }
const std::string& service_certificate() const override { return empty_; }
void set_service_certificate(const std::string&) override {}
bool is_session_sharing_enabled() const override { return false; }
uint32_t session_sharing_id() const override { return 0; }
void set_session_sharing_id(uint32_t /* id */) override {}
const std::string& app_id() const override { return app_id_; }
void set_app_id(const std::string& appId) { app_id_ = appId; }
bool use_atsc_mode() const override { return false; }
private:
std::string app_id_;
std::string security_level_;
const std::string empty_;
};
CdmEngine::CdmEngine(FileSystem* file_system,
std::shared_ptr<metrics::EngineMetrics> metrics)
: metrics_(metrics),
cert_provisioning_(),
file_system_(file_system),
spoid_(EMPTY_SPOID),
usage_session_(),
usage_property_set_(),
last_usage_information_update_time_(0) {
assert(file_system);
Properties::Init();
}
CdmEngine::~CdmEngine() {
usage_session_.reset();
std::unique_lock<std::recursive_mutex> lock(session_map_lock_);
session_map_.Terminate();
}
CdmResponseType CdmEngine::OpenSession(const CdmKeySystem& key_system,
CdmClientPropertySet* property_set,
const CdmSessionId& forced_session_id,
WvCdmEventListener* event_listener) {
return OpenSession(key_system, property_set, event_listener,
&forced_session_id, nullptr);
}
CdmResponseType CdmEngine::OpenSession(const CdmKeySystem& key_system,
CdmClientPropertySet* property_set,
WvCdmEventListener* event_listener,
CdmSessionId* session_id) {
return OpenSession(key_system, property_set, event_listener, nullptr,
session_id);
}
CdmResponseType CdmEngine::OpenSession(const CdmKeySystem& key_system,
CdmClientPropertySet* property_set,
WvCdmEventListener* event_listener,
const CdmSessionId* forced_session_id,
CdmSessionId* session_id) {
if (!ValidateKeySystem(key_system)) {
LOGI("Invalid key system: %s", IdToString(key_system));
return INVALID_KEY_SYSTEM;
}
if (session_id == nullptr && forced_session_id == nullptr) {
LOGE("Input |forced_session_id| and output |session_id| are both null");
return PARAMETER_NULL;
}
if (forced_session_id != nullptr) {
if (forced_session_id->empty()) {
// This should be enforce by the CE CDM code.
return EMPTY_SESSION_ID;
}
if (session_map_.Exists(*forced_session_id)) {
return DUPLICATE_SESSION_ID_SPECIFIED;
}
LOGD("forced_session_id = %s", IdPtrToString(forced_session_id));
}
CloseExpiredReleaseSessions();
std::unique_ptr<CdmSession> new_session(
new CdmSession(file_system_, metrics_->AddSession()));
const CdmResponseType sts =
new_session->Init(property_set, forced_session_id, event_listener);
if (sts == NEED_PROVISIONING) {
// Reserve a session ID so the CDM can return success.
if (session_id) *session_id = new_session->GenerateSessionId();
return sts;
}
if (sts != NO_ERROR) {
LOGE("Bad session init: status = %d", static_cast<int>(sts));
return sts;
}
const CdmSessionId id = new_session->session_id();
LOGI("New session: session_id = %s", IdToString(id));
std::unique_lock<std::recursive_mutex> lock(session_map_lock_);
session_map_.Add(id, new_session.release());
if (session_id) *session_id = id;
return NO_ERROR;
}
CdmResponseType CdmEngine::OpenKeySetSession(
const CdmKeySetId& key_set_id, CdmClientPropertySet* property_set,
WvCdmEventListener* event_listener) {
LOGI("key_set_id = %s", IdToString(key_set_id));
if (key_set_id.empty()) {
LOGE("Invalid key set ID");
return EMPTY_KEYSET_ID_ENG_1;
}
// If in-use, release key set before re-opening, to avoid leaking
// resources (CryptoSession etc).
bool key_set_in_use = false;
{
std::unique_lock<std::mutex> lock(release_key_sets_lock_);
key_set_in_use =
release_key_sets_.find(key_set_id) != release_key_sets_.end();
}
if (key_set_in_use) {
LOGD("Reopening existing key session");
CloseKeySetSession(key_set_id);
}
CdmSessionId session_id;
const CdmResponseType sts =
OpenSession(KEY_SYSTEM, property_set, event_listener,
nullptr /* forced_session_id */, &session_id);
if (sts != NO_ERROR) return sts;
std::unique_lock<std::mutex> lock(release_key_sets_lock_);
release_key_sets_[key_set_id] = std::make_pair(
session_id, clock_.GetCurrentTime() + kReleaseSessionTimeToLive);
return NO_ERROR;
}
CdmResponseType CdmEngine::CloseSession(const CdmSessionId& session_id) {
LOGI("session_id = %s", IdToString(session_id));
std::unique_lock<std::recursive_mutex> lock(session_map_lock_);
if (!session_map_.CloseSession(session_id)) {
LOGE("Session not found: session_id = %s", IdToString(session_id));
return SESSION_NOT_FOUND_1;
}
metrics_->ConsolidateSessions();
return NO_ERROR;
}
CdmResponseType CdmEngine::CloseKeySetSession(const CdmKeySetId& key_set_id) {
LOGI("key_set_id = %s", IdToString(key_set_id));
CdmSessionId session_id;
{
std::unique_lock<std::mutex> lock(release_key_sets_lock_);
CdmReleaseKeySetMap::iterator iter = release_key_sets_.find(key_set_id);
if (iter == release_key_sets_.end()) {
LOGE("Key set not found: key_set_id = %s", IdToString(key_set_id));
return KEYSET_ID_NOT_FOUND_1;
}
session_id = iter->second.first;
}
const CdmResponseType sts = CloseSession(session_id);
std::unique_lock<std::mutex> lock(release_key_sets_lock_);
CdmReleaseKeySetMap::iterator iter = release_key_sets_.find(key_set_id);
if (iter != release_key_sets_.end()) {
release_key_sets_.erase(iter);
}
return sts;
}
bool CdmEngine::IsOpenSession(const CdmSessionId& session_id) {
std::unique_lock<std::recursive_mutex> lock(session_map_lock_);
return session_map_.Exists(session_id);
}
CdmResponseType CdmEngine::GenerateKeyRequest(
const CdmSessionId& session_id, const CdmKeySetId& key_set_id,
const InitializationData& init_data, const CdmLicenseType license_type,
CdmAppParameterMap& app_parameters, CdmKeyRequest* key_request) {
LOGI("session_id = %s, key_set_id = %s, license_type = %s",
IdToString(session_id), IdToString(key_set_id),
CdmLicenseTypeToString(license_type));
if (key_request == nullptr) {
LOGE("Output |key_request| is null");
return PARAMETER_NULL;
}
CdmSessionId id = session_id;
// NOTE: If AlwaysUseKeySetIds() is true, there is no need to consult the
// |release_key_sets_| map for release licenses.
if (license_type == kLicenseTypeRelease &&
!Properties::AlwaysUseKeySetIds()) {
if (key_set_id.empty()) {
LOGE("Invalid key set ID");
return EMPTY_KEYSET_ID_ENG_2;
}
if (!session_id.empty()) {
LOGE("Session ID should be empty: session_id = %s",
IdToString(session_id));
return INVALID_SESSION_ID;
}
std::unique_lock<std::mutex> lock(release_key_sets_lock_);
CdmReleaseKeySetMap::iterator iter = release_key_sets_.find(key_set_id);
if (iter == release_key_sets_.end()) {
LOGE("Key set not found: key_set_id = %s", IdToString(key_set_id));
return KEYSET_ID_NOT_FOUND_2;
}
id = iter->second.first;
}
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(id, &session)) {
LOGE("Session not found: session_id = %s", IdToString(id));
return SESSION_NOT_FOUND_2;
}
key_request->message.clear();
if (license_type == kLicenseTypeRelease && !session->license_received()) {
int error_detail = NO_ERROR;
const CdmResponseType restore_status = session->RestoreOfflineSession(
key_set_id, kLicenseTypeRelease, &error_detail);
session->GetMetrics()->cdm_session_restore_offline_session_.Increment(
restore_status, error_detail);
if (restore_status != KEY_ADDED) {
LOGE("Key release restoration failed: session_id = %s, status = %d",
IdToString(id), static_cast<int>(restore_status));
return restore_status;
}
}
const CdmResponseType sts = session->GenerateKeyRequest(
init_data, license_type, app_parameters, key_request);
if (KEY_ADDED == sts) {
return sts;
}
if (KEY_MESSAGE != sts) {
LOGE("CdmSession::GenerateKeyRequest failed: session_id = %s, status = %d",
IdToString(id), static_cast<int>(sts));
return sts;
}
if (license_type == kLicenseTypeRelease) {
OnKeyReleaseEvent(key_set_id);
}
LOGD("key_request = (%zu) %s", key_request->message.size(),
wvcdm::Base64SafeEncode(key_request->message).c_str());
return KEY_MESSAGE;
}
CdmResponseType CdmEngine::AddKey(const CdmSessionId& session_id,
const CdmKeyResponse& key_data,
CdmLicenseType* license_type,
CdmKeySetId* key_set_id) {
LOGI("session_id = %s, key_set_id = %s", IdToString(session_id),
IdPtrToString(key_set_id));
if (license_type == nullptr) {
LOGE("Output |license_type| is null");
return PARAMETER_NULL;
}
CdmSessionId id = session_id;
const bool license_type_release = session_id.empty();
if (license_type_release) {
if (key_set_id == nullptr) {
LOGE("Input/output |key_set_id| is null");
return PARAMETER_NULL;
}
if (key_set_id->empty()) {
LOGE("Invalid key set ID");
return EMPTY_KEYSET_ID_ENG_3;
}
std::unique_lock<std::mutex> lock(release_key_sets_lock_);
CdmReleaseKeySetMap::iterator iter = release_key_sets_.find(*key_set_id);
if (iter == release_key_sets_.end()) {
LOGE("Key set not found: key_set_id = %s", IdPtrToString(key_set_id));
return KEYSET_ID_NOT_FOUND_3;
}
id = iter->second.first;
} else {
LOGD("key_data = (%zu) %s", key_data.size(),
wvcdm::Base64SafeEncode(key_data).c_str());
}
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(id, &session)) {
LOGE("Session not found: session_id = %s", IdToString(id));
return SESSION_NOT_FOUND_3;
}
if (key_data.empty()) {
LOGE("No key data");
return EMPTY_KEY_DATA_1;
}
const CdmResponseType sts = session->AddKey(key_data);
if (sts == KEY_ADDED) {
if (session->is_release()) {
*license_type = kLicenseTypeRelease;
} else if (session->is_temporary()) {
*license_type = kLicenseTypeTemporary;
} else if (session->is_offline()) {
*license_type = kLicenseTypeOffline;
} else {
*license_type = kLicenseTypeStreaming;
}
}
if (key_set_id != nullptr) {
if ((session->is_offline() || session->has_provider_session_token()) &&
!license_type_release) {
*key_set_id = session->key_set_id();
LOGI("key_set_id = %s", IdPtrToString(key_set_id));
} else {
key_set_id->clear();
}
}
switch (sts) {
case KEY_ADDED:
break;
case NEED_KEY:
LOGI("Service certificate loaded, no key added: session_id = %s",
IdToString(id));
break;
default:
LOGE("CdmSession::AddKey failed: session_id = %s, status = %d",
IdToString(id), static_cast<int>(sts));
break;
}
return sts;
}
CdmResponseType CdmEngine::RestoreKey(const CdmSessionId& session_id,
const CdmKeySetId& key_set_id) {
LOGI("session_id = %s, key_set_id = %s", IdToString(session_id),
IdToString(key_set_id));
if (key_set_id.empty()) {
LOGI("Invalid key set ID");
return EMPTY_KEYSET_ID_ENG_4;
}
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(session_id, &session)) {
LOGE("Session not found: session_id = %s", IdToString(session_id));
return SESSION_NOT_FOUND_4;
}
int error_detail = NO_ERROR;
const CdmResponseType sts = session->RestoreOfflineSession(
key_set_id, kLicenseTypeOffline, &error_detail);
session->GetMetrics()->cdm_session_restore_offline_session_.Increment(
sts, error_detail);
if (sts != KEY_ADDED && sts != GET_RELEASED_LICENSE_ERROR) {
LOGE("Restore offline session failed: session_id = %s, status = %d",
IdToString(session_id), static_cast<int>(sts));
}
return sts;
}
CdmResponseType CdmEngine::RemoveKeys(const CdmSessionId& session_id) {
LOGI("session_id = %s", IdToString(session_id));
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(session_id, &session)) {
LOGE("Session not found: session_id = %s", IdToString(session_id));
return SESSION_NOT_FOUND_5;
}
session->RemoveKeys();
return NO_ERROR;
}
CdmResponseType CdmEngine::RemoveLicense(const CdmSessionId& session_id) {
LOGI("session_id = %s", IdToString(session_id));
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(session_id, &session)) {
LOGE("Session not found: session_id = %s", IdToString(session_id));
return SESSION_NOT_FOUND_19;
}
return session->RemoveLicense();
}
CdmResponseType CdmEngine::GenerateRenewalRequest(
const CdmSessionId& session_id, CdmKeyRequest* key_request) {
LOGI("session_id = %s", IdToString(session_id));
if (key_request == nullptr) {
LOGE("Output |key_request| is null");
return PARAMETER_NULL;
}
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(session_id, &session)) {
LOGE("Session not found: session_id = %s", IdToString(session_id));
return SESSION_NOT_FOUND_6;
}
key_request->message.clear();
const CdmResponseType sts = session->GenerateRenewalRequest(key_request);
if (KEY_MESSAGE != sts) {
LOGE("Failed: session_id = %s, status = %d", IdToString(session_id),
static_cast<int>(sts));
return sts;
}
return KEY_MESSAGE;
}
CdmResponseType CdmEngine::RenewKey(const CdmSessionId& session_id,
const CdmKeyResponse& key_data) {
LOGI("session_id = %s", IdToString(session_id));
if (key_data.empty()) {
LOGE("No key data");
return EMPTY_KEY_DATA_2;
}
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(session_id, &session)) {
LOGE("Session not found: session_id = %s", IdToString(session_id));
return SESSION_NOT_FOUND_7;
}
CdmResponseType sts;
M_TIME(sts = session->RenewKey(key_data), session->GetMetrics(),
cdm_session_renew_key_, sts);
if (KEY_ADDED != sts) {
LOGE("Failed: session_id = %s, status = %d", IdToString(session_id),
static_cast<int>(sts));
return sts;
}
return KEY_ADDED;
}
CdmResponseType CdmEngine::SetSessionServiceCertificate(
const CdmSessionId& session_id, const std::string& service_certificate) {
LOGI("session_id = %s", IdToString(session_id));
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(session_id, &session)) {
LOGE("Session not found: session_id = %s", IdToString(session_id));
return SESSION_NOT_FOUND_22;
}
return session->SetServiceCertificate(service_certificate);
}
CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level,
const std::string& query_token,
std::string* query_response) {
LOGD("security_level = %s, query_token = %s",
SecurityLevelToString(security_level), IdToString(query_token));
if (query_response == nullptr) {
LOGE("Output |query_response| is null");
return PARAMETER_NULL;
}
std::unique_ptr<CryptoSession> crypto_session(
CryptoSession::MakeCryptoSession(metrics_->GetCryptoMetrics()));
// Add queries here, that can be answered before a session is opened
if (query_token == QUERY_KEY_SECURITY_LEVEL) {
const CdmSecurityLevel found_security_level =
crypto_session->GetSecurityLevel(security_level);
switch (found_security_level) {
case kSecurityLevelL1:
*query_response = QUERY_VALUE_SECURITY_LEVEL_L1;
break;
case kSecurityLevelL2:
*query_response = QUERY_VALUE_SECURITY_LEVEL_L2;
break;
case kSecurityLevelL3:
*query_response = QUERY_VALUE_SECURITY_LEVEL_L3;
break;
case kSecurityLevelUninitialized:
case kSecurityLevelUnknown:
*query_response = QUERY_VALUE_SECURITY_LEVEL_UNKNOWN;
break;
default:
LOGW("Unknown security level: %d",
static_cast<int>(found_security_level));
return UNKNOWN_ERROR;
}
return NO_ERROR;
}
if (query_token == QUERY_KEY_CURRENT_HDCP_LEVEL ||
query_token == QUERY_KEY_MAX_HDCP_LEVEL) {
CryptoSession::HdcpCapability current_hdcp;
CryptoSession::HdcpCapability max_hdcp;
const CdmResponseType status = crypto_session->GetHdcpCapabilities(
security_level, &current_hdcp, &max_hdcp);
if (status != NO_ERROR) {
LOGW("GetHdcpCapabilities failed: status = %d", static_cast<int>(status));
return status;
}
*query_response = MapHdcpVersion(
query_token == QUERY_KEY_CURRENT_HDCP_LEVEL ? current_hdcp : max_hdcp);
return NO_ERROR;
}
if (query_token == QUERY_KEY_USAGE_SUPPORT) {
bool supports_usage_reporting;
const bool got_info = crypto_session->HasUsageInfoSupport(
security_level, &supports_usage_reporting);
if (!got_info) {
LOGW("HasUsageInfoSupport failed");
metrics_->GetCryptoMetrics()
->crypto_session_usage_information_support_.SetError(got_info);
return UNKNOWN_ERROR;
}
metrics_->GetCryptoMetrics()
->crypto_session_usage_information_support_.Record(
supports_usage_reporting);
*query_response =
supports_usage_reporting ? QUERY_VALUE_TRUE : QUERY_VALUE_FALSE;
return NO_ERROR;
}
if (query_token == QUERY_KEY_NUMBER_OF_OPEN_SESSIONS) {
size_t number_of_open_sessions;
const CdmResponseType status = crypto_session->GetNumberOfOpenSessions(
security_level, &number_of_open_sessions);
if (status != NO_ERROR) {
LOGW("GetNumberOfOpenSessions failed: status = %d",
static_cast<int>(status));
return status;
}
*query_response = std::to_string(number_of_open_sessions);
return NO_ERROR;
}
if (query_token == QUERY_KEY_MAX_NUMBER_OF_SESSIONS) {
size_t maximum_number_of_sessions = 0;
const CdmResponseType status = crypto_session->GetMaxNumberOfSessions(
security_level, &maximum_number_of_sessions);
if (status != NO_ERROR) {
LOGW("GetMaxNumberOfOpenSessions failed: status = %d",
static_cast<int>(status));
return status;
}
*query_response = std::to_string(maximum_number_of_sessions);
return NO_ERROR;
}
if (query_token == QUERY_KEY_OEMCRYPTO_API_VERSION) {
uint32_t api_version;
if (!crypto_session->GetApiVersion(security_level, &api_version)) {
LOGW("GetApiVersion failed");
return UNKNOWN_ERROR;
}
*query_response = std::to_string(api_version);
return NO_ERROR;
}
if (query_token == QUERY_KEY_CURRENT_SRM_VERSION) {
uint16_t current_srm_version;
const CdmResponseType status =
crypto_session->GetSrmVersion(&current_srm_version);
if (status == NO_ERROR) {
*query_response = std::to_string(current_srm_version);
return NO_ERROR;
}
if (status == NO_SRM_VERSION) {
// SRM is not supported or not applicable (ex. local display only).
*query_response = QUERY_VALUE_NONE;
return NO_ERROR;
}
LOGW("GetCurrentSRMVersion failed: status = %d", static_cast<int>(status));
return status;
}
if (query_token == QUERY_KEY_SRM_UPDATE_SUPPORT) {
const bool is_srm_update_supported = crypto_session->IsSrmUpdateSupported();
*query_response =
is_srm_update_supported ? QUERY_VALUE_TRUE : QUERY_VALUE_FALSE;
return NO_ERROR;
}
if (query_token == QUERY_KEY_WVCDM_VERSION) {
std::string cdm_version;
if (!Properties::GetWVCdmVersion(&cdm_version)) {
LOGW("GetWVCdmVersion failed");
return UNKNOWN_ERROR;
}
*query_response = cdm_version;
return NO_ERROR;
}
if (query_token == QUERY_KEY_RESOURCE_RATING_TIER) {
uint32_t tier;
if (!crypto_session->GetResourceRatingTier(security_level, &tier)) {
LOGW("GetResourceRatingTier failed");
return UNKNOWN_ERROR;
}
*query_response = std::to_string(tier);
return NO_ERROR;
}
if (query_token == QUERY_KEY_OEMCRYPTO_BUILD_INFORMATION) {
if (!crypto_session->GetBuildInformation(security_level, query_response)) {
LOGW("GetBuildInformation failed");
query_response->clear();
return UNKNOWN_ERROR;
}
return NO_ERROR;
}
if (query_token == QUERY_KEY_DECRYPT_HASH_SUPPORT) {
uint32_t hash_support = 0;
if (!crypto_session->GetDecryptHashSupport(security_level, &hash_support)) {
LOGW("GetDecryptHashSupport failed");
return UNKNOWN_ERROR;
}
*query_response = std::to_string(hash_support);
return NO_ERROR;
}
if (query_token == QUERY_KEY_PROVISIONING_MODEL) {
CdmClientTokenType token_type = kClientTokenUninitialized;
const CdmResponseType status =
crypto_session->GetProvisioningMethod(security_level, &token_type);
if (status != NO_ERROR) {
LOGW("GetProvisioningMethod failed: status = %d",
static_cast<int>(status));
return status;
}
switch (token_type) {
case kClientTokenDrmCert:
*query_response = QUERY_VALUE_DRM_CERTIFICATE;
break;
case kClientTokenKeybox:
*query_response = QUERY_VALUE_KEYBOX;
break;
case kClientTokenOemCert:
*query_response = QUERY_VALUE_OEM_CERTIFICATE;
break;
case kClientTokenUninitialized:
default:
LOGW("GetProvisioningMethod returned invalid method: token_type = %d",
static_cast<int>(token_type));
return GET_PROVISIONING_METHOD_ERROR;
}
return NO_ERROR;
}
if (query_token == QUERY_KEY_MAX_USAGE_TABLE_ENTRIES) {
size_t max_number_of_usage_entries;
if (!crypto_session->GetMaximumUsageTableEntries(
security_level, &max_number_of_usage_entries)) {
LOGW("GetMaxUsageTableEntries failed");
return UNKNOWN_ERROR;
}
if (max_number_of_usage_entries == 0) {
// Zero indicates that the table is dynamically allocated and does
// not have a defined limit. Setting to max value of int32_t to
// be able to fit into a Java int.
max_number_of_usage_entries = std::numeric_limits<int32_t>::max();
}
*query_response = std::to_string(max_number_of_usage_entries);
return NO_ERROR;
}
if (query_token == QUERY_KEY_OEMCRYPTO_API_MINOR_VERSION) {
uint32_t api_minor_version;
if (!crypto_session->GetApiMinorVersion(security_level,
&api_minor_version)) {
LOGW("GetApiMinorVersion failed");
return UNKNOWN_ERROR;
}
*query_response = std::to_string(api_minor_version);
return NO_ERROR;
}
if (query_token == QUERY_KEY_ANALOG_OUTPUT_CAPABILITIES) {
bool supported = false, can_disable = false, cgms_a = false;
if (crypto_session->GetAnalogOutputCapabilities(&supported, &can_disable,
&cgms_a)) {
if (supported) {
if (cgms_a) {
*query_response = QUERY_VALUE_CGMS_A;
} else {
*query_response = QUERY_VALUE_SUPPORTED;
}
} else {
*query_response = QUERY_VALUE_NONE;
}
} else {
*query_response = QUERY_VALUE_UNKNOWN;
}
return NO_ERROR;
}
if (query_token == QUERY_KEY_CAN_DISABLE_ANALOG_OUTPUT) {
bool supported = false, can_disable = false, cgms_a = false;
if (crypto_session->GetAnalogOutputCapabilities(&supported, &can_disable,
&cgms_a)) {
*query_response = can_disable ? QUERY_VALUE_TRUE : QUERY_VALUE_FALSE;
} else {
*query_response = QUERY_VALUE_UNKNOWN;
}
return NO_ERROR;
}
CdmResponseType status;
M_TIME(status = crypto_session->Open(security_level),
metrics_->GetCryptoMetrics(), crypto_session_open_, status,
security_level);
if (status != NO_ERROR) return status;
// Add queries here, that need an open session before they can be answered
if (query_token == QUERY_KEY_DEVICE_ID) {
std::string device_id;
status = crypto_session->GetExternalDeviceUniqueId(&device_id);
metrics_->GetCryptoMetrics()
->crypto_session_get_device_unique_id_.Increment(status);
if (status != NO_ERROR) return status;
*query_response = device_id;
return NO_ERROR;
}
if (query_token == QUERY_KEY_SYSTEM_ID) {
uint32_t system_id;
const bool got_id = crypto_session->GetSystemId(&system_id);
if (!got_id) {
LOGW("QUERY_KEY_SYSTEM_ID unknown failure");
return UNKNOWN_ERROR;
}
*query_response = std::to_string(system_id);
return NO_ERROR;
}
if (query_token == QUERY_KEY_PROVISIONING_ID) {
std::string provisioning_id;
status = crypto_session->GetProvisioningId(&provisioning_id);
if (status != NO_ERROR) {
LOGW("GetProvisioningId failed: status = %d", static_cast<int>(status));
return status;
}
*query_response = provisioning_id;
return NO_ERROR;
}
LOGW("Unknown status requested: query_token = %s", IdToString(query_token));
return INVALID_QUERY_KEY;
}
CdmResponseType CdmEngine::QuerySessionStatus(const CdmSessionId& session_id,
CdmQueryMap* query_response) {
LOGI("session_id = %s", IdToString(session_id));
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(session_id, &session)) {
LOGE("Session not found: session_id = %s", IdToString(session_id));
return SESSION_NOT_FOUND_8;
}
return session->QueryStatus(query_response);
}
bool CdmEngine::IsReleaseSession(const CdmSessionId& session_id) {
LOGI("session_id = %s", IdToString(session_id));
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(session_id, &session)) {
LOGE("Session not found: session_id = %s", IdToString(session_id));
return false;
}
return session->is_release();
}
bool CdmEngine::IsOfflineSession(const CdmSessionId& session_id) {
LOGI("session_id = %s", IdToString(session_id));
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(session_id, &session)) {
LOGE("Session not found: session_id = %s", IdToString(session_id));
return false;
}
return session->is_offline();
}
CdmResponseType CdmEngine::QueryKeyStatus(const CdmSessionId& session_id,
CdmQueryMap* query_response) {
LOGI("session_id = %s", IdToString(session_id));
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(session_id, &session)) {
LOGE("Session not found: session_id = %s", IdToString(session_id));
return SESSION_NOT_FOUND_9;
}
return session->QueryKeyStatus(query_response);
}
CdmResponseType CdmEngine::QueryKeyAllowedUsage(const CdmSessionId& session_id,
const std::string& key_id,
CdmKeyAllowedUsage* key_usage) {
LOGI("session_id = %s, key_id = %s", IdToString(session_id),
IdToString(key_id));
if (key_usage == nullptr) {
LOGE("Output |key_usage| is null");
return PARAMETER_NULL;
}
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(session_id, &session)) {
LOGE("Session not found: session_id = %s", IdToString(session_id));
return SESSION_NOT_FOUND_12;
}
return session->QueryKeyAllowedUsage(key_id, key_usage);
}
CdmResponseType CdmEngine::QueryKeyAllowedUsage(const std::string& key_id,
CdmKeyAllowedUsage* key_usage) {
LOGI("key_id = %s", IdToString(key_id));
if (!key_usage) {
LOGE("Output |key_usage| is null");
return PARAMETER_NULL;
}
key_usage->Clear();
CdmSessionList sessions;
session_map_.GetSessionList(sessions);
bool found = false;
for (CdmSessionList::iterator iter = sessions.begin(); iter != sessions.end();
++iter) {
CdmKeyAllowedUsage found_in_this_session;
const CdmResponseType sts =
(*iter)->QueryKeyAllowedUsage(key_id, &found_in_this_session);
if (sts == NO_ERROR) {
if (found) {
// Found another key. If usage settings do not match, fail.
if (!key_usage->Equals(found_in_this_session)) {
key_usage->Clear();
return KEY_CONFLICT_1;
}
} else {
*key_usage = found_in_this_session;
found = true;
}
} else if (sts != KEY_NOT_FOUND_1) {
LOGE("QueryKeyAllowedUsage failed: status = %d", static_cast<int>(sts));
key_usage->Clear();
return sts;
}
}
return (found) ? NO_ERROR : KEY_NOT_FOUND_2;
}
CdmResponseType CdmEngine::QueryOemCryptoSessionId(
const CdmSessionId& session_id, CdmQueryMap* query_response) {
LOGI("session_id = %s", IdToString(session_id));
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(session_id, &session)) {
LOGE("Session not found: session_id = %s", IdToString(session_id));
return SESSION_NOT_FOUND_10;
}
return session->QueryOemCryptoSessionId(query_response);
}
bool CdmEngine::IsSecurityLevelSupported(CdmSecurityLevel level) {
LOGI("level = %s", CdmSecurityLevelToString(level));
metrics::CryptoMetrics alternate_crypto_metrics;
std::unique_ptr<CryptoSession> crypto_session(
CryptoSession::MakeCryptoSession(&alternate_crypto_metrics));
if (level == kSecurityLevelL1) {
return crypto_session->GetSecurityLevel(kLevelDefault) == kSecurityLevelL1;
}
if (level == kSecurityLevelL3) {
return crypto_session->GetSecurityLevel(kLevel3) == kSecurityLevelL3;
}
LOGE("Unsupported value: level = %d", static_cast<int>(level));
return false;
}
/*
* Composes a device provisioning request and output the request in JSON format
* in *request. It also returns the default url for the provisioning server
* in *default_url.
*
* Returns NO_ERROR for success and CdmResponseType error code if fails.
*/
CdmResponseType CdmEngine::GetProvisioningRequest(
CdmCertificateType cert_type, const std::string& cert_authority,
const std::string& service_certificate,
SecurityLevel requested_security_level, CdmProvisioningRequest* request,
std::string* default_url) {
LOGI("cert_type = %s", CdmCertificateTypeToString(cert_type));
if (!request) {
LOGE("Output |request| is null");
return INVALID_PROVISIONING_REQUEST_PARAM_1;
}
if (!default_url) {
LOGE("Output |default_url| is null");
return INVALID_PROVISIONING_REQUEST_PARAM_2;
}
// TODO(b/141705730): Remove usage entries on provisioning.
if (!cert_provisioning_) {
cert_provisioning_.reset(
new CertificateProvisioning(metrics_->GetCryptoMetrics()));
const CdmResponseType status =
cert_provisioning_->Init(service_certificate);
if (status != NO_ERROR) return status;
}
const CdmResponseType status = cert_provisioning_->GetProvisioningRequest(
requested_security_level, cert_type, cert_authority,
file_system_->origin(), spoid_, request, default_url);
if (status != NO_ERROR) {
cert_provisioning_.reset(); // Release resources.
}
return status;
}
/*
* The response message consists of a device certificate and the device RSA key.
* The device RSA key is stored in the T.E.E. The device certificate is stored
* in the device.
*
* Returns NO_ERROR for success and CdmResponseType error code if fails.
*/
CdmResponseType CdmEngine::HandleProvisioningResponse(
const CdmProvisioningResponse& response,
SecurityLevel requested_security_level, std::string* cert,
std::string* wrapped_key) {
LOGI("response_size = %zu, security_level = %s", response.size(),
SecurityLevelToString(requested_security_level));
if (response.empty()) {
LOGE("Empty provisioning response");
cert_provisioning_.reset();
return EMPTY_PROVISIONING_RESPONSE;
}
if (cert == nullptr) {
LOGE("Output |cert| is null");
cert_provisioning_.reset();
return INVALID_PROVISIONING_PARAMETERS_1;
}
if (wrapped_key == nullptr) {
LOGE("Output |wrapped_key| is null");
cert_provisioning_.reset();
return INVALID_PROVISIONING_PARAMETERS_2;
}
if (!cert_provisioning_) {
// Certificate provisioning object has been released. Check if a concurrent
// provisioning attempt has succeeded before declaring failure.
std::unique_ptr<CryptoSession> crypto_session(
CryptoSession::MakeCryptoSession(metrics_->GetCryptoMetrics()));
CdmResponseType status;
M_TIME(status = crypto_session->Open(requested_security_level),
metrics_->GetCryptoMetrics(), crypto_session_open_, status,
requested_security_level);
if (NO_ERROR != status) {
LOGE("Provisioning object missing and crypto session open failed");
return EMPTY_PROVISIONING_CERTIFICATE_2;
}
CdmSecurityLevel security_level = crypto_session->GetSecurityLevel();
if (!IsProvisioned(security_level)) {
LOGE("Provisioning object missing");
return EMPTY_PROVISIONING_CERTIFICATE_1;
}
return NO_ERROR;
}
const CdmResponseType ret = cert_provisioning_->HandleProvisioningResponse(
file_system_, response, cert, wrapped_key);
// Release resources only on success. It is possible that a provisioning
// attempt was made after this one was requested but before the response was
// received, which will cause this attempt to fail. Not releasing will
// allow for the possibility that the later attempt succeeds.
if (NO_ERROR == ret) cert_provisioning_.reset();
return ret;
}
bool CdmEngine::IsProvisioned(CdmSecurityLevel security_level) {
// To validate whether the given security level is provisioned, we attempt to
// initialize a CdmSession. This verifies the existence of a certificate and
// attempts to load it. If this fails, initialization will return an error.
UsagePropertySet property_set;
property_set.set_security_level(
security_level == kSecurityLevelL3 ? kLevel3 : kLevelDefault);
CdmSession session(file_system_, metrics_->AddSession());
const CdmResponseType status = session.Init(&property_set);
if (NO_ERROR != status) {
LOGE("Init failed: status = %d", static_cast<int>(status));
}
return status == NO_ERROR;
}
CdmResponseType CdmEngine::Unprovision(CdmSecurityLevel security_level) {
LOGI("security_level = %s", CdmSecurityLevelToString(security_level));
// Devices with baked-in DRM certs cannot be reprovisioned and therefore must
// not be unprovisioned.
std::unique_ptr<CryptoSession> crypto_session(
CryptoSession::MakeCryptoSession(metrics_->GetCryptoMetrics()));
CdmClientTokenType token_type = kClientTokenUninitialized;
const CdmResponseType res = crypto_session->GetProvisioningMethod(
security_level == kSecurityLevelL3 ? kLevel3 : kLevelDefault,
&token_type);
if (res != NO_ERROR) {
return res;
}
if (token_type == kClientTokenDrmCert) {
return DEVICE_CANNOT_REPROVISION;
}
DeviceFiles handle(file_system_);
if (!handle.Init(security_level)) {
LOGE("Unable to initialize device files");
return UNPROVISION_ERROR_1;
}
// TODO(b/141705730): Remove usage entries during unprovisioning.
if (!file_system_->IsGlobal()) {
if (!handle.RemoveCertificate()) {
LOGE("Unable to delete certificate");
return UNPROVISION_ERROR_2;
}
return NO_ERROR;
}
if (!handle.DeleteAllFiles()) {
LOGE("Unable to delete files");
return UNPROVISION_ERROR_3;
}
return NO_ERROR;
}
CdmResponseType CdmEngine::ListStoredLicenses(
CdmSecurityLevel security_level, std::vector<std::string>* key_set_ids) {
DeviceFiles handle(file_system_);
if (!key_set_ids) {
LOGE("Output |key_set_ids| is null");
return INVALID_PARAMETERS_ENG_22;
}
if (!handle.Init(security_level)) {
LOGE("Unable to initialize device files");
return LIST_LICENSE_ERROR_1;
}
if (!handle.ListLicenses(key_set_ids)) {
LOGE("ListLicenses call failed");
return LIST_LICENSE_ERROR_2;
}
return NO_ERROR;
}
CdmResponseType CdmEngine::ListUsageIds(
const std::string& app_id, CdmSecurityLevel security_level,
std::vector<std::string>* ksids,
std::vector<std::string>* provider_session_tokens) {
DeviceFiles handle(file_system_);
if (!ksids && !provider_session_tokens) {
LOGE("Outputs |ksids| and |provider_session_tokens| are null");
return INVALID_PARAMETERS_ENG_23;
}
if (!handle.Init(security_level)) {
LOGE("Unable to initialize device files");
return LIST_USAGE_ERROR_1;
}
if (!handle.ListUsageIds(app_id, ksids, provider_session_tokens)) {
LOGE("Failed: app_id = %s, security_level = %s", IdToString(app_id),
CdmSecurityLevelToString(security_level));
return LIST_USAGE_ERROR_2;
}
return NO_ERROR;
}
CdmResponseType CdmEngine::DeleteUsageRecord(const std::string& app_id,
CdmSecurityLevel security_level,
const std::string& key_set_id) {
LOGI("app_id = %s, key_set_id = %s", IdToString(app_id),
IdToString(key_set_id));
DeviceFiles handle(file_system_);
if (!handle.Init(security_level)) {
LOGE("Unable to initialize device files");
return DELETE_USAGE_ERROR_1;
}
std::string provider_session_token;
if (!handle.GetProviderSessionToken(app_id, key_set_id,
&provider_session_token)) {
LOGE("GetProviderSessionToken failed");
return DELETE_USAGE_ERROR_2;
}
return RemoveUsageInfo(app_id, provider_session_token);
}
CdmResponseType CdmEngine::GetOfflineLicenseState(
const CdmKeySetId& key_set_id, CdmSecurityLevel security_level,
CdmOfflineLicenseState* license_state) {
DeviceFiles handle(file_system_);
if (!handle.Init(security_level)) {
LOGE("Cannot initialize device files");
return GET_OFFLINE_LICENSE_STATE_ERROR_1;
}
DeviceFiles::CdmLicenseData license_data;
DeviceFiles::ResponseType sub_error_code = DeviceFiles::kNoError;
if (!handle.RetrieveLicense(key_set_id, &license_data, &sub_error_code)) {
LOGE("Failed to retrieve license state: key_set_id = %s",
IdToString(key_set_id));
return GET_OFFLINE_LICENSE_STATE_ERROR_2;
}
*license_state = MapDeviceFilesLicenseState(license_data.state);
return NO_ERROR;
}
CdmResponseType CdmEngine::RemoveOfflineLicense(
const CdmKeySetId& key_set_id, CdmSecurityLevel security_level) {
UsagePropertySet property_set;
property_set.set_security_level(
security_level == kSecurityLevelL3 ? kLevel3 : kLevelDefault);
DeviceFiles handle(file_system_);
if (!handle.Init(security_level)) {
LOGE("Cannot initialize device files: security_level = %s",
security_level == kSecurityLevelL3 ? "L3" : "Default");
return REMOVE_OFFLINE_LICENSE_ERROR_1;
}
CdmResponseType sts = OpenKeySetSession(key_set_id, &property_set,
nullptr /* event listener */);
if (sts != NO_ERROR) {
LOGE("OpenKeySetSession failed: status = %d", static_cast<int>(sts));
handle.DeleteLicense(key_set_id);
return sts;
}
CdmSessionId session_id;
CdmAppParameterMap dummy_app_params;
const InitializationData dummy_init_data("", "", "");
CdmKeyRequest key_request;
// Calling with no session_id is okay
sts = GenerateKeyRequest(session_id, key_set_id, dummy_init_data,
kLicenseTypeRelease, dummy_app_params, &key_request);
if (sts == KEY_MESSAGE) {
std::unique_lock<std::mutex> lock(release_key_sets_lock_);
CdmReleaseKeySetMap::iterator iter = release_key_sets_.find(key_set_id);
if (iter == release_key_sets_.end()) {
LOGE("Key set not found: key_set_id = %s", IdToString(key_set_id));
sts = REMOVE_OFFLINE_LICENSE_ERROR_2;
} else {
session_id = iter->second.first;
sts = RemoveLicense(session_id);
}
} else if (sts == LICENSE_USAGE_ENTRY_MISSING) {
// It is possible that the CDM is tracking a key set ID, but has
// removed the usage information associated with it. In this case,
// it will no longer be possible to load the license for release;
// and the file should simply be deleted.
LOGW("License usage entry is missing, deleting license file");
handle.DeleteLicense(key_set_id);
sts = NO_ERROR;
}
if (sts != NO_ERROR) {
LOGE("GenerateKeyRequest failed: status = %d", static_cast<int>(sts));
handle.DeleteLicense(key_set_id);
}
CloseKeySetSession(key_set_id);
return sts;
}
CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id,
const CdmSecureStopId& ssid,
int* error_detail,
CdmUsageInfo* usage_info) {
LOGI("app_id = %s, ssid = %s", IdToString(app_id), IdToString(ssid));
if (!usage_property_set_) {
usage_property_set_.reset(new UsagePropertySet());
}
if (usage_info == nullptr) {
LOGE("Output |usage_info| is null");
return PARAMETER_NULL;
}
usage_property_set_->set_security_level(kLevelDefault);
usage_property_set_->set_app_id(app_id);
usage_session_.reset(new CdmSession(file_system_, metrics_->AddSession()));
CdmResponseType status = usage_session_->Init(usage_property_set_.get());
if (NO_ERROR != status) {
LOGE("Session init error: status = %d", static_cast<int>(status));
return status;
}
DeviceFiles handle(file_system_);
if (!handle.Init(usage_session_->GetSecurityLevel())) {
LOGE("Device file init error");
return GET_USAGE_INFO_ERROR_1;
}
DeviceFiles::CdmUsageData usage_data;
if (!handle.RetrieveUsageInfo(DeviceFiles::GetUsageInfoFileName(app_id), ssid,
&usage_data)) {
usage_property_set_->set_security_level(kLevel3);
usage_property_set_->set_app_id(app_id);
usage_session_.reset(new CdmSession(file_system_, metrics_->AddSession()));
status = usage_session_->Init(usage_property_set_.get());
if (NO_ERROR != status) {
LOGE("Session init error: status = %d", static_cast<int>(status));
return status;
}
if (!handle.Reset(usage_session_->GetSecurityLevel())) {
LOGE("Device file init error");
return GET_USAGE_INFO_ERROR_2;
}
if (!handle.RetrieveUsageInfo(DeviceFiles::GetUsageInfoFileName(app_id),
ssid, &usage_data)) {
// No entry found for that ssid.
return USAGE_INFO_NOT_FOUND;
}
}
status = usage_session_->RestoreUsageSession(usage_data, error_detail);
if (KEY_ADDED != status) {
LOGE("RestoreUsageSession failed: status = %d", static_cast<int>(status));
usage_info->clear();
return status;
}
CdmKeyRequest request;
status = usage_session_->GenerateReleaseRequest(&request);
usage_info->clear();
usage_info->push_back(request.message);
if (KEY_MESSAGE != status) {
LOGE("GenerateReleaseRequest failed: status = %d",
static_cast<int>(status));
usage_info->clear();
return status;
}
return KEY_MESSAGE;
}
CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id,
int* error_detail,
CdmUsageInfo* usage_info) {
LOGI("app_id = %s", IdToString(app_id));
if (usage_info == nullptr) {
LOGE("Output |usage_info| is null");
return PARAMETER_NULL;
}
// Return a random usage report from a random security level
SecurityLevel security_level =
CdmRandom::RandomBool() ? kLevelDefault : kLevel3;
CdmResponseType status = UNKNOWN_ERROR;
do {
status = GetUsageInfo(app_id, security_level, error_detail, usage_info);
if (KEY_MESSAGE == status && !usage_info->empty()) {
return status;
}
} while (KEY_CANCELED == status);
security_level = (kLevel3 == security_level) ? kLevelDefault : kLevel3;
do {
status = GetUsageInfo(app_id, security_level, error_detail, usage_info);
if (NEED_PROVISIONING == status)
return NO_ERROR; // Valid scenario that one of the security
// levels has not been provisioned
} while (KEY_CANCELED == status);
return status;
}
CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id,
SecurityLevel requested_security_level,
int* error_detail,
CdmUsageInfo* usage_info) {
LOGI("app_id = %s, security_level = %s", IdToString(app_id),
SecurityLevelToString(requested_security_level));
if (usage_info == nullptr) {
LOGE("Output |usage_info| is null");
return PARAMETER_NULL;
}
if (!usage_property_set_) {
usage_property_set_.reset(new UsagePropertySet());
}
usage_property_set_->set_security_level(requested_security_level);
usage_property_set_->set_app_id(app_id);
usage_session_.reset(new CdmSession(file_system_, metrics_->AddSession()));
CdmResponseType status = usage_session_->Init(usage_property_set_.get());
if (NO_ERROR != status) {
LOGE("Session init error");
return status;
}
DeviceFiles handle(file_system_);
if (!handle.Init(usage_session_->GetSecurityLevel())) {
LOGE("Unable to initialize device files");
return GET_USAGE_INFO_ERROR_3;
}
std::vector<DeviceFiles::CdmUsageData> usage_data;
if (!handle.RetrieveUsageInfo(DeviceFiles::GetUsageInfoFileName(app_id),
&usage_data)) {
LOGE("Unable to read usage information");
return GET_USAGE_INFO_ERROR_4;
}
if (usage_data.empty()) {
usage_info->clear();
return NO_ERROR;
}
const size_t index = CdmRandom::RandomInRange(usage_data.size() - 1);
status = usage_session_->RestoreUsageSession(usage_data[index], error_detail);
if (KEY_ADDED != status) {
// TODO(b/141704872): Make multiple attempts.
LOGE("RestoreUsageSession failed: index = %zu, status = %d", index,
static_cast<int>(status));
usage_info->clear();
return status;
}
CdmKeyRequest request;
status = usage_session_->GenerateReleaseRequest(&request);
usage_info->clear();
usage_info->push_back(request.message);
switch (status) {
case KEY_MESSAGE:
break;
case KEY_CANCELED: // usage information not present in
usage_session_->DeleteLicenseFile(); // OEMCrypto, delete and try again
usage_info->clear();
break;
default:
LOGE("GenerateReleaseRequest failed: status = %d",
static_cast<int>(status));
usage_info->clear();
break;
}
return status;
}
CdmResponseType CdmEngine::RemoveAllUsageInfo(
const std::string& app_id, CdmSecurityLevel cdm_security_level) {
LOGI("app_id = %s, security_level = %s", IdToString(app_id),
CdmSecurityLevelToString(cdm_security_level));
if (!usage_property_set_) {
usage_property_set_.reset(new UsagePropertySet());
}
usage_property_set_->set_app_id(app_id);
CdmResponseType status = NO_ERROR;
DeviceFiles handle(file_system_);
if (handle.Init(cdm_security_level)) {
const SecurityLevel security_level =
cdm_security_level == kSecurityLevelL3 ? kLevel3 : kLevelDefault;
usage_property_set_->set_security_level(security_level);
usage_session_.reset(new CdmSession(file_system_, metrics_->AddSession()));
usage_session_->Init(usage_property_set_.get());
if (usage_session_->supports_usage_info()) {
std::vector<DeviceFiles::CdmUsageData> usage_data;
// Retrieve all usage information but delete only one before
// refetching. This is because deleting the usage entry
// might cause other entries to be shifted and information updated.
do {
if (!handle.RetrieveUsageInfo(DeviceFiles::GetUsageInfoFileName(app_id),
&usage_data)) {
LOGW("Failed to retrieve usage info");
break;
}
if (usage_data.empty()) break;
CdmResponseType res =
usage_session_->DeleteUsageEntry(usage_data[0].usage_entry_number);
if (res != NO_ERROR) {
LOGW("Failed to delete usage entry: status = %d",
static_cast<int>(res));
break;
}
if (!handle.DeleteUsageInfo(DeviceFiles::GetUsageInfoFileName(app_id),
usage_data[0].provider_session_token)) {
LOGW("Failed to delete usage info");
break;
}
} while (!usage_data.empty());
std::vector<std::string> provider_session_tokens;
if (!handle.DeleteAllUsageInfoForApp(
DeviceFiles::GetUsageInfoFileName(app_id),
&provider_session_tokens)) {
status = REMOVE_ALL_USAGE_INFO_ERROR_5;
}
}
}
usage_session_.reset();
return status;
}
CdmResponseType CdmEngine::RemoveAllUsageInfo(const std::string& app_id) {
LOGI("app_id = %s", IdToString(app_id));
const CdmResponseType status_l1 =
RemoveAllUsageInfo(app_id, kSecurityLevelL1);
const CdmResponseType status_l3 =
RemoveAllUsageInfo(app_id, kSecurityLevelL3);
// Prioritizing L1 status.
if (status_l1 != NO_ERROR) {
return status_l1;
}
return status_l3;
}
CdmResponseType CdmEngine::RemoveUsageInfo(
const std::string& app_id, const CdmSecureStopId& provider_session_token) {
LOGI("app_id = %s, pst = %s", IdToString(app_id),
IdToString(provider_session_token));
if (!usage_property_set_) {
usage_property_set_.reset(new UsagePropertySet());
}
usage_property_set_->set_app_id(app_id);
CdmResponseType status = NO_ERROR;
for (int j = kSecurityLevelL1; j < kSecurityLevelUnknown; ++j) {
DeviceFiles handle(file_system_);
if (handle.Init(static_cast<CdmSecurityLevel>(j))) {
SecurityLevel security_level =
static_cast<CdmSecurityLevel>(j) == kSecurityLevelL3 ? kLevel3
: kLevelDefault;
usage_property_set_->set_security_level(security_level);
usage_session_.reset(
new CdmSession(file_system_, metrics_->AddSession()));
usage_session_->Init(usage_property_set_.get());
CdmKeyMessage license_request;
CdmKeyResponse license_response;
CdmUsageEntry usage_entry;
uint32_t usage_entry_number;
std::string drm_certificate;
CryptoWrappedKey wrapped_private_key;
if (!handle.RetrieveUsageInfo(
DeviceFiles::GetUsageInfoFileName(app_id), provider_session_token,
&license_request, &license_response, &usage_entry,
&usage_entry_number, &drm_certificate, &wrapped_private_key)) {
// Try other security level
continue;
}
if (usage_session_->supports_usage_info()) {
status = usage_session_->DeleteUsageEntry(usage_entry_number);
if (!handle.DeleteUsageInfo(DeviceFiles::GetUsageInfoFileName(app_id),
provider_session_token)) {
status = REMOVE_USAGE_INFO_ERROR_1;
}
usage_session_.reset();
return status;
}
} else {
LOGE("Failed to initialize L%d device files", j);
status = REMOVE_USAGE_INFO_ERROR_2;
}
}
usage_session_.reset();
return REMOVE_USAGE_INFO_ERROR_3;
}
CdmResponseType CdmEngine::ReleaseUsageInfo(
const CdmUsageInfoReleaseMessage& message) {
LOGI("message_size = %zu", message.size());
if (!usage_session_) {
LOGE("Usage session not initialized");
return RELEASE_USAGE_INFO_ERROR;
}
const CdmResponseType status = usage_session_->ReleaseKey(message);
usage_session_.reset();
if (NO_ERROR != status) {
LOGE("ReleaseKey failed: status = %d", status);
}
return status;
}
CdmResponseType CdmEngine::LoadUsageSession(const CdmKeySetId& key_set_id,
CdmKeyMessage* release_message) {
LOGI("key_set_id = %s", IdToString(key_set_id));
// This method is currently only used by the CE CDM, in which all session IDs
// are key set IDs.
assert(Properties::AlwaysUseKeySetIds());
if (key_set_id.empty()) {
LOGE("Invalid key set ID");
return EMPTY_KEYSET_ID_ENG_5;
}
if (release_message == nullptr) {
LOGE("Output |release_message| is null");
return PARAMETER_NULL;
}
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(key_set_id, &session)) {
LOGE("Session not found: key_set_id = %s", IdToString(key_set_id));
return SESSION_NOT_FOUND_11;
}
DeviceFiles handle(file_system_);
if (!handle.Init(session->GetSecurityLevel())) {
LOGE("Unable to initialize device files");
return LOAD_USAGE_INFO_FILE_ERROR;
}
std::string app_id;
session->GetApplicationId(&app_id);
DeviceFiles::CdmUsageData usage_data;
if (!handle.RetrieveUsageInfoByKeySetId(
DeviceFiles::GetUsageInfoFileName(app_id), key_set_id,
&(usage_data.provider_session_token), &(usage_data.license_request),
&(usage_data.license), &(usage_data.usage_entry),
&(usage_data.usage_entry_number), &(usage_data.drm_certificate),
&(usage_data.wrapped_private_key))) {
LOGE("Unable to find usage information");
return LOAD_USAGE_INFO_MISSING;
}
int error_detail = NO_ERROR;
usage_data.key_set_id = key_set_id;
CdmResponseType status =
session->RestoreUsageSession(usage_data, &error_detail);
session->GetMetrics()->cdm_session_restore_usage_session_.Increment(
status, error_detail);
if (KEY_ADDED != status) {
LOGE("Restore failed: key_set_id = %s, status = %d", IdToString(key_set_id),
static_cast<int>(status));
return status;
}
CdmKeyRequest request;
status = session->GenerateReleaseRequest(&request);
*release_message = std::move(request.message);
switch (status) {
case KEY_MESSAGE:
break;
case KEY_CANCELED:
// usage information not present in OEMCrypto, delete and try again
session->DeleteLicenseFile();
break;
default:
LOGE("GenerateReleaseRequest failed: status = %d",
static_cast<int>(status));
break;
}
return status;
}
CdmResponseType CdmEngine::DecryptV16(
const CdmSessionId& session_id,
const CdmDecryptionParametersV16& parameters) {
for (const CdmDecryptionSample& sample : parameters.samples) {
if (sample.encrypt_buffer == nullptr) {
LOGE("No src encrypt buffer");
return INVALID_DECRYPT_PARAMETERS_ENG_2;
}
if (sample.decrypt_buffer == nullptr) {
if (!parameters.is_secure &&
!Properties::Properties::oem_crypto_use_fifo()) {
LOGE("No dest decrypt buffer");
return INVALID_DECRYPT_PARAMETERS_ENG_4;
}
// else we must be level 1 direct and we don't need to return a buffer.
}
}
std::unique_lock<std::recursive_mutex> lock(session_map_lock_);
std::shared_ptr<CdmSession> session;
if (session_id.empty()) {
CdmSessionList sessions;
session_map_.GetSessionList(sessions);
// Loop through the sessions to find the session containing the key_id
// with the longest remaining license validity.
int64_t seconds_remaining = 0;
for (CdmSessionList::iterator iter = sessions.begin();
iter != sessions.end(); ++iter) {
if ((*iter)->IsKeyLoaded(parameters.key_id)) {
int64_t duration = (*iter)->GetDurationRemaining();
if (duration > seconds_remaining) {
session = *iter;
seconds_remaining = duration;
}
}
}
if (!session) {
LOGE("Session not found: session_id = <empty>");
return SESSION_NOT_FOUND_FOR_DECRYPT;
}
} else {
if (!session_map_.FindSession(session_id, &session)) {
LOGE("Session not found: session_id = %s", IdToString(session_id));
return SESSION_NOT_FOUND_FOR_DECRYPT;
}
}
return session->Decrypt(parameters);
}
CdmResponseType CdmEngine::GenericEncrypt(const std::string& session_id,
const std::string& in_buffer,
const std::string& key_id,
const std::string& iv,
CdmEncryptionAlgorithm algorithm,
std::string* out_buffer) {
if (out_buffer == nullptr) {
LOGE("Output |out_buffer| is null");
return PARAMETER_NULL;
}
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(session_id, &session)) {
LOGE("Session not found: session_id = %s", IdToString(session_id));
return SESSION_NOT_FOUND_13;
}
return session->GenericEncrypt(in_buffer, key_id, iv, algorithm, out_buffer);
}
CdmResponseType CdmEngine::GenericDecrypt(const std::string& session_id,
const std::string& in_buffer,
const std::string& key_id,
const std::string& iv,
CdmEncryptionAlgorithm algorithm,
std::string* out_buffer) {
if (out_buffer == nullptr) {
LOGE("Output |out_buffer| is null");
return PARAMETER_NULL;
}
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(session_id, &session)) {
LOGE("Session not found: session_id = %s", IdToString(session_id));
return SESSION_NOT_FOUND_14;
}
return session->GenericDecrypt(in_buffer, key_id, iv, algorithm, out_buffer);
}
CdmResponseType CdmEngine::GenericSign(const std::string& session_id,
const std::string& message,
const std::string& key_id,
CdmSigningAlgorithm algorithm,
std::string* signature) {
if (signature == nullptr) {
LOGE("Output |signature| is null");
return PARAMETER_NULL;
}
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(session_id, &session)) {
LOGE("Session not found: session_id = %s", IdToString(session_id));
return SESSION_NOT_FOUND_15;
}
return session->GenericSign(message, key_id, algorithm, signature);
}
CdmResponseType CdmEngine::GenericVerify(const std::string& session_id,
const std::string& message,
const std::string& key_id,
CdmSigningAlgorithm algorithm,
const std::string& signature) {
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(session_id, &session)) {
LOGE("Session not found: session_id = %s", IdToString(session_id));
return SESSION_NOT_FOUND_16;
}
return session->GenericVerify(message, key_id, algorithm, signature);
}
CdmResponseType CdmEngine::ParseDecryptHashString(
const std::string& hash_string, CdmSessionId* session_id,
uint32_t* frame_number, std::string* hash) {
if (session_id == nullptr) {
LOGE("Output |session_id| is null");
return PARAMETER_NULL;
}
if (frame_number == nullptr) {
LOGE("Output |frame_number| is null");
return PARAMETER_NULL;
}
if (hash == nullptr) {
LOGE("Output |hash| is null");
return PARAMETER_NULL;
}
std::stringstream ss;
std::string token;
std::vector<std::string> tokens;
ss.str(hash_string);
while (getline(ss, token, ',')) {
tokens.push_back(token);
}
if (tokens.size() != 3) {
LOGE(
"Hash string has invalid format: "
"Unexpected number of tokens: %zu (hash_string = %s)",
tokens.size(), hash_string.c_str());
return INVALID_DECRYPT_HASH_FORMAT;
}
for (size_t i = 0; i < tokens.size(); ++i) {
if (tokens[i].empty()) {
LOGE(
"Hash string has invalid format: token %zu of length 0: "
"hash_string = %s",
i, hash_string.c_str());
return INVALID_DECRYPT_HASH_FORMAT;
}
}
*session_id = tokens[0];
std::istringstream iss(tokens[1]);
if (!(iss >> *frame_number)) {
LOGE("Error while trying to convert frame number to a numeric format: %s",
hash_string.c_str());
return INVALID_DECRYPT_HASH_FORMAT;
}
*hash = wvcdm::a2bs_hex(tokens[2]);
if (hash->empty()) {
LOGE("Malformed hash: %s", hash_string.c_str());
return INVALID_DECRYPT_HASH_FORMAT;
}
return NO_ERROR;
}
CdmResponseType CdmEngine::SetDecryptHash(const CdmSessionId& session_id,
uint32_t frame_number,
const std::string& hash) {
LOGI("session_id = %s", IdToString(session_id));
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(session_id, &session)) {
return SESSION_NOT_FOUND_20;
}
return session->SetDecryptHash(frame_number, hash);
}
CdmResponseType CdmEngine::GetDecryptHashError(const CdmSessionId& session_id,
std::string* error_string) {
LOGI("session_id = %s", IdToString(session_id));
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(session_id, &session)) {
return SESSION_NOT_FOUND_20;
}
return session->GetDecryptHashError(error_string);
}
// TODO(gmorgan) Used? Delete if unused.
bool CdmEngine::IsKeyLoaded(const KeyId& key_id) {
CdmSessionList sessions;
session_map_.GetSessionList(sessions);
for (CdmSessionList::iterator iter = sessions.begin(); iter != sessions.end();
++iter) {
if ((*iter)->IsKeyLoaded(key_id)) {
return true;
}
}
return false;
}
bool CdmEngine::FindSessionForKey(const KeyId& key_id,
CdmSessionId* session_id) {
if (session_id == nullptr) {
LOGE("Output |session_id| is null");
return false;
}
const uint32_t session_sharing_id =
Properties::GetSessionSharingId(*session_id);
std::unique_lock<std::recursive_mutex> lock(session_map_lock_);
CdmSessionList sessions;
session_map_.GetSessionList(sessions);
CdmSessionList::iterator session_iter = sessions.end();
int64_t seconds_remaining = 0;
for (CdmSessionList::iterator iter = sessions.begin(); iter != sessions.end();
++iter) {
CdmSessionId id = (*iter)->session_id();
if (Properties::GetSessionSharingId(id) == session_sharing_id) {
if ((*iter)->IsKeyLoaded(key_id)) {
int64_t duration = (*iter)->GetDurationRemaining();
if (duration > seconds_remaining) {
session_iter = iter;
seconds_remaining = duration;
}
}
}
}
if (session_iter != sessions.end()) {
*session_id = (*session_iter)->session_id();
return true;
}
return false;
}
bool CdmEngine::NotifyResolution(const CdmSessionId& session_id, uint32_t width,
uint32_t height) {
std::shared_ptr<CdmSession> session;
if (session_map_.FindSession(session_id, &session)) {
session->NotifyResolution(width, height);
return true;
}
return false;
}
bool CdmEngine::ValidateKeySystem(const CdmKeySystem& key_system) {
return (key_system.find("widevine") != std::string::npos);
}
void CdmEngine::OnTimerEvent() {
Clock clock;
const uint64_t current_time = clock.GetCurrentTime();
bool usage_update_period_expired = false;
if (current_time - last_usage_information_update_time_ >
kUpdateUsageInformationPeriod) {
usage_update_period_expired = true;
last_usage_information_update_time_ = current_time;
}
bool is_initial_usage_update = false;
bool is_usage_update_needed = false;
{
std::unique_lock<std::recursive_mutex> lock(session_map_lock_);
CdmSessionList sessions;
session_map_.GetSessionList(sessions);
while (!sessions.empty()) {
is_initial_usage_update = is_initial_usage_update ||
sessions.front()->is_initial_usage_update();
is_usage_update_needed =
is_usage_update_needed || sessions.front()->is_usage_update_needed();
sessions.front()->OnTimerEvent(usage_update_period_expired);
sessions.pop_front();
}
if (is_usage_update_needed &&
(usage_update_period_expired || is_initial_usage_update)) {
// Session list may have changed. Rebuild.
session_map_.GetSessionList(sessions);
for (CdmSessionList::iterator iter = sessions.begin();
iter != sessions.end(); ++iter) {
(*iter)->reset_usage_flags();
if ((*iter)->supports_usage_info() &&
(*iter)->has_provider_session_token()) {
(*iter)->UpdateUsageEntryInformation();
}
}
}
}
CloseExpiredReleaseSessions();
}
void CdmEngine::OnKeyReleaseEvent(const CdmKeySetId& key_set_id) {
CdmSessionList sessions;
session_map_.GetSessionList(sessions);
while (!sessions.empty()) {
sessions.front()->OnKeyReleaseEvent(key_set_id);
sessions.pop_front();
}
}
CdmResponseType CdmEngine::ValidateServiceCertificate(const std::string& cert) {
ServiceCertificate certificate;
return certificate.Init(cert);
}
CdmResponseType CdmEngine::SetPlaybackId(const CdmSessionId& session_id,
const std::string& playback_id) {
LOGI("session_id = %s, playback_id = %s", IdToString(session_id),
IdToString(playback_id));
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(session_id, &session)) {
LOGE("Session not found: session_id = %s", IdToString(session_id));
return SESSION_NOT_FOUND_23;
}
session->GetMetrics()->playback_id_.Record(playback_id);
return NO_ERROR;
}
std::string CdmEngine::MapHdcpVersion(CryptoSession::HdcpCapability version) {
switch (version) {
case HDCP_NONE:
return QUERY_VALUE_HDCP_NONE;
case HDCP_V1:
return QUERY_VALUE_HDCP_V1;
case HDCP_V2:
return QUERY_VALUE_HDCP_V2_0;
case HDCP_V2_1:
return QUERY_VALUE_HDCP_V2_1;
case HDCP_V2_2:
return QUERY_VALUE_HDCP_V2_2;
case HDCP_V2_3:
return QUERY_VALUE_HDCP_V2_3;
case HDCP_NO_DIGITAL_OUTPUT:
return QUERY_VALUE_HDCP_NO_DIGITAL_OUTPUT;
default:
return QUERY_VALUE_HDCP_LEVEL_UNKNOWN;
}
}
void CdmEngine::CloseExpiredReleaseSessions() {
const int64_t current_time = clock_.GetCurrentTime();
std::set<CdmSessionId> close_session_set;
{
std::unique_lock<std::mutex> lock(release_key_sets_lock_);
for (CdmReleaseKeySetMap::iterator iter = release_key_sets_.begin();
iter != release_key_sets_.end();) {
if (iter->second.second < current_time) {
close_session_set.insert(iter->second.first);
release_key_sets_.erase(iter++);
} else {
++iter;
}
}
}
for (std::set<CdmSessionId>::iterator iter = close_session_set.begin();
iter != close_session_set.end(); ++iter) {
CloseSession(*iter);
}
}
} // namespace wvcdm