Files
android/libwvdrmengine/cdm/core/src/cdm_engine.cpp
Alex Dale 1603ba127f Renaming of Usage Table related variables and types.
[ Merge of http://go/wvgerrit/164077 ]

This CL makes major changes to the names of variables and types that
are related to the usage table, header, entries, entry indexes, and
other related data.

The renaming followed these rules:
1)  "Usage table header" will exclusively refer to the header blob
    that is OEMCrypto specific.  The CDM class "UsageTableHeader"
    is the CDM-layer's abstraction around the "usage table" concept.
    The name has been updated to reflect that.
2)  The "Cdm" prefix is only used for the CDM-specific data types for
    the usage table and entry info.  It has been removed from
    OEMCrypto-specific types.
    - UsageTableHeader -> CdmUsageTable
    - CdmUsageTableHeader -> UsageTableHeader
    - CdmUsageEntry -> UsageEntry
3)  The "usage_" prefix has been removed from variables when the usage
    table or usage entries are the subject of the function or class.
4)  UsageEntryIndex is the type for entry indexes, instead of directly
    using uint32_t.  This matches how we wrap other types in
    "wv_cdm_types.h"
5)  Changed entry "number" to entry "index".
6)  Vectors of elements have been renamed to be either pluralized or
    have a suffix "_list".
7)  "Usage info" was occasionally being used to refer to the usage
    table or entries generally, rather than specifically secure-stop.
    - CryptoSession::HasUsageInfoSupport() -> HasUsageTableSupport()

The most major change is that the files "usage_table_header*" have
been renamed to be "cdm_usage_table*".

Bug: 242914226
Test: run_x86_64_tests and request_license_test
Change-Id: Iee98446b71f4f2934d3c9e0fb949eb05b84d1f8c
2022-12-21 15:50:49 -08:00

2297 lines
82 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 "okp_fallback_policy.h"
#include "ota_keybox_provisioner.h"
#include "properties.h"
#include "string_conversions.h"
#include "system_id_extractor.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
} // namespace
class UsagePropertySet : public CdmClientPropertySet {
public:
UsagePropertySet() {}
~UsagePropertySet() override {}
void set_security_level(RequestedSecurityLevel 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(wvutil::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 CdmResponseType(INVALID_KEY_SYSTEM);
}
if (session_id == nullptr && forced_session_id == nullptr) {
LOGE("Input |forced_session_id| and output |session_id| are both null");
return CdmResponseType(PARAMETER_NULL);
}
if (forced_session_id != nullptr) {
if (forced_session_id->empty()) {
// This should be enforce by the CE CDM code.
return CdmResponseType(EMPTY_SESSION_ID);
}
if (session_map_.Exists(*forced_session_id)) {
return CdmResponseType(DUPLICATE_SESSION_ID_SPECIFIED);
}
LOGD("forced_session_id = %s", IdPtrToString(forced_session_id));
}
RequestedSecurityLevel requested_security_level = kLevelDefault;
if (property_set &&
property_set->security_level() == QUERY_VALUE_SECURITY_LEVEL_L3) {
requested_security_level = kLevel3;
}
bool forced_level3 = false;
if (requested_security_level == kLevelDefault) {
if (OkpCheck()) {
bool okp_provisioned = false;
bool fallback = false;
{
std::unique_lock<std::mutex> lock(okp_mutex_);
if (!okp_provisioner_) {
// Very rare race condition. Possible if two calls to OpenSession
// occur the same time. Cleanup would have been performed.
if (okp_fallback_) {
fallback = true;
} else {
okp_provisioned = true;
}
} else if (okp_provisioner_->IsProvisioned()) {
okp_provisioned = true;
} else if (okp_provisioner_->IsInFallbackMode()) {
fallback = true;
}
}
if (okp_provisioned) {
// OKP not required, engine may assume normal operations.
OkpCleanUp();
} else if (fallback) {
LOGD("Engine is falling back to L3");
OkpTriggerFallback();
forced_level3 = true;
} else {
// OKP is required.
return CdmResponseType(NEED_PROVISIONING);
}
} else {
std::unique_lock<std::mutex> lock(okp_mutex_);
// |okp_fallback_| would have been set previously if required.
if (okp_fallback_) forced_level3 = true;
}
}
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, forced_level3);
if (sts != NO_ERROR) {
if (sts == NEED_PROVISIONING) {
// Reserve a session ID so the CDM can return success.
if (session_id) *session_id = new_session->GenerateSessionId();
} else {
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 CdmResponseType(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 CdmResponseType(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 CdmResponseType(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 CdmResponseType(SESSION_NOT_FOUND_1);
}
metrics_->ConsolidateSessions();
return CdmResponseType(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 CdmResponseType(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 CdmResponseType(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 CdmResponseType(EMPTY_KEYSET_ID_ENG_2);
}
if (!session_id.empty()) {
LOGE("Session ID should be empty: session_id = %s",
IdToString(session_id));
return CdmResponseType(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 CdmResponseType(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 CdmResponseType(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(),
wvutil::Base64SafeEncode(key_request->message).c_str());
return CdmResponseType(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 CdmResponseType(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 CdmResponseType(PARAMETER_NULL);
}
if (key_set_id->empty()) {
LOGE("Invalid key set ID");
return CdmResponseType(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 CdmResponseType(KEYSET_ID_NOT_FOUND_3);
}
id = iter->second.first;
} else {
LOGD("key_data = (%zu) %s", key_data.size(),
wvutil::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 CdmResponseType(SESSION_NOT_FOUND_3);
}
if (key_data.empty()) {
LOGE("No key data");
return CdmResponseType(EMPTY_KEY_DATA_1);
}
CdmResponseType sts(KEY_ADDED);
{
// TODO(rfrias): Refactor. For now lock while adding keys to prevent
// a race condition between this and the decryption thread. This may
// occur if |policy_timers_| is reset when PolicyEngine::SetLicense
// is called.
std::unique_lock<std::recursive_mutex> lock(session_map_lock_);
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.Enum()) {
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 CdmResponseType(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 CdmResponseType(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;
std::unique_lock<std::recursive_mutex> lock(session_map_lock_);
if (!session_map_.FindSession(session_id, &session)) {
LOGE("Session not found: session_id = %s", IdToString(session_id));
return CdmResponseType(SESSION_NOT_FOUND_5);
}
session->RemoveKeys();
return CdmResponseType(NO_ERROR);
}
CdmResponseType CdmEngine::RemoveLicense(const CdmSessionId& session_id) {
LOGI("session_id = %s", IdToString(session_id));
std::shared_ptr<CdmSession> session;
std::unique_lock<std::recursive_mutex> lock(session_map_lock_);
if (!session_map_.FindSession(session_id, &session)) {
LOGE("Session not found: session_id = %s", IdToString(session_id));
return CdmResponseType(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 CdmResponseType(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 CdmResponseType(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 CdmResponseType(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 CdmResponseType(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 CdmResponseType(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 CdmResponseType(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 CdmResponseType(SESSION_NOT_FOUND_22);
}
return session->SetServiceCertificate(service_certificate);
}
CdmResponseType CdmEngine::QueryStatus(RequestedSecurityLevel security_level,
const std::string& query_token,
std::string* query_response) {
LOGD("security_level = %s, query_token = %s",
RequestedSecurityLevelToString(security_level), IdToString(query_token));
if (query_response == nullptr) {
LOGE("Output |query_response| is null");
return CdmResponseType(PARAMETER_NULL);
}
std::unique_ptr<CryptoSession> crypto_session(
CryptoSession::MakeCryptoSession(metrics_->GetCryptoMetrics()));
// Force OKP check on CryptoSession. Only concerned if engine
// has fallen back to L3.
if (security_level == kLevelDefault && OkpIsInFallbackMode()) {
LOGD("Engine is falling back to L3 for query: token = %s",
query_token.c_str());
security_level = kLevel3;
}
// 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 CdmResponseType(UNKNOWN_ERROR);
}
return CdmResponseType(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 CdmResponseType(NO_ERROR);
}
if (query_token == QUERY_KEY_USAGE_SUPPORT) {
bool supports_usage_reporting;
const bool got_info = crypto_session->HasUsageTableSupport(
security_level, &supports_usage_reporting);
if (!got_info) {
LOGW("HasUsageTableSupport failed");
metrics_->GetCryptoMetrics()
->crypto_session_usage_information_support_.SetError(got_info);
return CdmResponseType(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 CdmResponseType(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 CdmResponseType(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 CdmResponseType(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 CdmResponseType(UNKNOWN_ERROR);
}
*query_response = std::to_string(api_version);
return CdmResponseType(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 CdmResponseType(NO_ERROR);
}
if (status == NO_SRM_VERSION) {
// SRM is not supported or not applicable (ex. local display only).
*query_response = QUERY_VALUE_NONE;
return CdmResponseType(NO_ERROR);
}
LOGW("GetCurrentSRMVersion failed: status = %d", static_cast<int>(status));
return status;
}
if (query_token == QUERY_KEY_SRM_UPDATE_SUPPORT) {
*query_response = QUERY_VALUE_FALSE;
return CdmResponseType(NO_ERROR);
}
if (query_token == QUERY_KEY_WVCDM_VERSION) {
std::string cdm_version;
if (!Properties::GetWVCdmVersion(&cdm_version)) {
LOGW("GetWVCdmVersion failed");
return CdmResponseType(UNKNOWN_ERROR);
}
*query_response = cdm_version;
return CdmResponseType(NO_ERROR);
}
if (query_token == QUERY_KEY_RESOURCE_RATING_TIER) {
uint32_t tier;
if (!crypto_session->GetResourceRatingTier(security_level, &tier)) {
LOGW("GetResourceRatingTier failed");
return CdmResponseType(UNKNOWN_ERROR);
}
*query_response = std::to_string(tier);
return CdmResponseType(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 CdmResponseType(UNKNOWN_ERROR);
}
return CdmResponseType(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 CdmResponseType(UNKNOWN_ERROR);
}
*query_response = std::to_string(hash_support);
return CdmResponseType(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 kClientTokenBootCertChain:
*query_response = QUERY_VALUE_BOOT_CERTIFICATE_CHAIN;
break;
case kClientTokenUninitialized:
default:
LOGW("GetProvisioningMethod returned invalid method: token_type = %d",
static_cast<int>(token_type));
return CdmResponseType(GET_PROVISIONING_METHOD_ERROR);
}
return CdmResponseType(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 CdmResponseType(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 CdmResponseType(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 CdmResponseType(UNKNOWN_ERROR);
}
*query_response = std::to_string(api_minor_version);
return CdmResponseType(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 CdmResponseType(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 CdmResponseType(NO_ERROR);
}
if (query_token == QUERY_KEY_WATERMARKING_SUPPORT) {
CdmWatermarkingSupport support;
if (!crypto_session->GetWatermarkingSupport(security_level, &support)) {
// Assume not supported.
support = kWatermarkingNotSupported;
}
switch (support) {
case kWatermarkingNotSupported:
*query_response = QUERY_VALUE_NOT_SUPPORTED;
break;
case kWatermarkingConfigurable:
*query_response = QUERY_VALUE_CONFIGURABLE;
break;
case kWatermarkingAlwaysOn:
*query_response = QUERY_VALUE_ALWAYS_ON;
break;
default:
LOGW("Unknown watermarking support: %d", static_cast<int>(support));
return CdmResponseType(UNKNOWN_ERROR);
}
return CdmResponseType(NO_ERROR);
}
if (query_token == QUERY_KEY_PRODUCTION_READY) {
CdmProductionReadiness readiness;
if (!crypto_session->GetProductionReadiness(security_level, &readiness)) {
LOGW("GetProductionReadiness failed");
return CdmResponseType(UNKNOWN_ERROR);
}
switch (readiness) {
case kProductionReadinessUnknown:
*query_response = QUERY_VALUE_UNKNOWN;
break;
case kProductionReadinessTrue:
*query_response = QUERY_VALUE_TRUE;
break;
case kProductionReadinessFalse:
*query_response = QUERY_VALUE_FALSE;
break;
default:
LOGW("Unknown readiness: %d", static_cast<int>(readiness));
return CdmResponseType(UNKNOWN_ERROR);
}
return CdmResponseType(NO_ERROR);
}
if (query_token == QUERY_KEY_SYSTEM_ID) {
wvutil::FileSystem global_file_system;
SystemIdExtractor extractor(security_level, crypto_session.get(),
&global_file_system);
uint32_t system_id;
if (!extractor.ExtractSystemId(&system_id)) {
LOGW("ExtractSystemId failed");
return CdmResponseType(UNKNOWN_ERROR);
}
*query_response = std::to_string(system_id);
return CdmResponseType(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 CdmResponseType(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 CdmResponseType(NO_ERROR);
}
LOGW("Unknown status requested: query_token = %s", IdToString(query_token));
return CdmResponseType(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 CdmResponseType(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 CdmResponseType(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 CdmResponseType(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 CdmResponseType(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 CdmResponseType(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 CdmResponseType(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) ? CdmResponseType(NO_ERROR) : CdmResponseType(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 CdmResponseType(SESSION_NOT_FOUND_10);
}
return session->QueryOemCryptoSessionId(query_response);
}
// static
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,
RequestedSecurityLevel requested_security_level,
CdmProvisioningRequest* request, std::string* default_url) {
LOGI("cert_type = %s", CdmCertificateTypeToString(cert_type));
if (!request) {
LOGE("Output |request| is null");
return CdmResponseType(INVALID_PROVISIONING_REQUEST_PARAM_1);
}
if (!default_url) {
LOGE("Output |default_url| is null");
return CdmResponseType(INVALID_PROVISIONING_REQUEST_PARAM_2);
}
if (requested_security_level == kLevelDefault) {
if (OkpCheck()) {
if (okp_provisioner_->IsProvisioned()) {
// OKP not required, engine may assume normal operations.
OkpCleanUp();
} else if (okp_provisioner_->IsInFallbackMode()) {
LOGD("Engine is falling back to L3");
OkpTriggerFallback();
requested_security_level = kLevel3;
} else {
// OKP is required.
const CdmResponseType status =
okp_provisioner_->GetProvisioningRequest(request, default_url);
if (status == NO_ERROR) return CdmResponseType(NO_ERROR);
if (status == NOT_IMPLEMENTED_ERROR) {
LOGW("OKP not supoprted, falling back to L3");
OkpTriggerFallback();
requested_security_level = kLevel3;
} else if (status == OKP_ALREADY_PROVISIONED) {
LOGD("OKP already completed, continuing in normal operation");
OkpCleanUp();
// Continue with normal provisioning request.
} else {
LOGE("Failed to generate OKP request: status = %d",
static_cast<int>(status));
return status;
}
}
} else {
std::unique_lock<std::mutex> lock(okp_mutex_);
if (okp_fallback_) {
requested_security_level = kLevel3;
}
}
}
// 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(
file_system_, 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,
RequestedSecurityLevel requested_security_level, std::string* cert,
std::string* wrapped_key) {
LOGI("response_size = %zu, security_level = %s", response.size(),
RequestedSecurityLevelToString(requested_security_level));
if (response.empty()) {
LOGE("Empty provisioning response");
cert_provisioning_.reset();
return CdmResponseType(EMPTY_PROVISIONING_RESPONSE);
}
if (cert == nullptr) {
LOGE("Output |cert| is null");
cert_provisioning_.reset();
return CdmResponseType(INVALID_PROVISIONING_PARAMETERS_1);
}
if (wrapped_key == nullptr) {
LOGE("Output |wrapped_key| is null");
cert_provisioning_.reset();
return CdmResponseType(INVALID_PROVISIONING_PARAMETERS_2);
}
if (requested_security_level == kLevelDefault) {
bool use_okp = false;
CdmResponseType okp_res(UNKNOWN_ERROR);
{
std::unique_lock<std::mutex> lock(okp_mutex_);
if (okp_provisioner_) {
use_okp = true;
// If the engine initiated OKP previously, it must complete it
// regardless of whether the device has fallen back to L3.
okp_res = okp_provisioner_->HandleProvisioningResponse(response);
} else if (okp_fallback_) {
requested_security_level = kLevel3;
}
}
if (use_okp) {
// Cannot hold lock when calling OkpCleanUp() or OkpTriggerFallback().
if (okp_res == NO_ERROR) {
OkpCleanUp();
} else {
OkpTriggerFallback();
}
return okp_res;
}
}
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 CdmResponseType(EMPTY_PROVISIONING_CERTIFICATE_2);
}
CdmSecurityLevel security_level = crypto_session->GetSecurityLevel();
if (!IsProvisioned(security_level)) {
LOGE("Provisioning object missing");
return CdmResponseType(EMPTY_PROVISIONING_CERTIFICATE_1);
}
return CdmResponseType(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) {
LOGI("security_level = %d", static_cast<int>(security_level));
if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) {
LOGD("OKP fallback to L3");
security_level = kSecurityLevelL3;
}
// 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);
return GetProvisioningStatus(security_level) == kProvisioned;
}
CdmProvisioningStatus CdmEngine::GetProvisioningStatus(
CdmSecurityLevel security_level) {
std::unique_ptr<CryptoSession> crypto_session(
CryptoSession::MakeCryptoSession(metrics_->GetCryptoMetrics()));
CdmResponseType status = crypto_session->Open(
security_level == kSecurityLevelL3 ? kLevel3 : kLevelDefault);
if (status != NO_ERROR) {
LOGE("Failed to open crypto session: status = %d",
static_cast<int>(status));
return kUnknownProvisionStatus;
}
const CdmSecurityLevel cdm_security_level =
crypto_session->GetSecurityLevel();
DeviceFiles handle(file_system_);
if (!handle.Init(cdm_security_level)) {
LOGE("Failed to initialize device files.");
return kUnknownProvisionStatus;
}
UsagePropertySet property_set;
if (handle.HasCertificate(property_set.use_atsc_mode())) {
return kProvisioned;
}
if (crypto_session->GetPreProvisionTokenType() == kClientTokenBootCertChain) {
wvutil::FileSystem global_file_system;
DeviceFiles global_handle(&global_file_system);
if (!global_handle.Init(cdm_security_level)) {
LOGE("Failed to initialize global device files.");
return kUnknownProvisionStatus;
}
if (!global_handle.HasOemCertificate()) {
return kNeedsOemCertProvisioning;
}
}
return kNeedsDrmCertProvisioning;
}
CdmResponseType CdmEngine::Unprovision(CdmSecurityLevel security_level) {
LOGI("security_level = %s", CdmSecurityLevelToString(security_level));
if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) {
LOGD("OKP fallback to L3");
security_level = kSecurityLevelL3;
}
// 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 CdmResponseType(DEVICE_CANNOT_REPROVISION);
}
DeviceFiles handle(file_system_);
if (!handle.Init(security_level)) {
LOGE("Unable to initialize device files");
return CdmResponseType(UNPROVISION_ERROR_1);
}
// TODO(b/141705730): Remove usage entries during unprovisioning.
if (!file_system_->IsGlobal()) {
if (!handle.RemoveCertificate() || !handle.RemoveOemCertificate()) {
LOGE("Unable to delete certificate");
return CdmResponseType(UNPROVISION_ERROR_2);
}
return CdmResponseType(NO_ERROR);
}
if (!handle.DeleteAllFiles()) {
LOGE("Unable to delete files");
return CdmResponseType(UNPROVISION_ERROR_3);
}
return CdmResponseType(NO_ERROR);
}
CdmResponseType CdmEngine::ListStoredLicenses(
CdmSecurityLevel security_level, std::vector<std::string>* key_set_ids) {
if (!key_set_ids) {
LOGE("Output |key_set_ids| is null");
return CdmResponseType(INVALID_PARAMETERS_ENG_22);
}
if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) {
LOGD("OKP fallback to L3");
security_level = kSecurityLevelL3;
}
DeviceFiles handle(file_system_);
if (!handle.Init(security_level)) {
LOGE("Unable to initialize device files");
return CdmResponseType(LIST_LICENSE_ERROR_1);
}
if (!handle.ListLicenses(key_set_ids)) {
LOGE("ListLicenses call failed");
return CdmResponseType(LIST_LICENSE_ERROR_2);
}
return CdmResponseType(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) {
if (!ksids && !provider_session_tokens) {
LOGE("Outputs |ksids| and |provider_session_tokens| are null");
return CdmResponseType(INVALID_PARAMETERS_ENG_23);
}
if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) {
LOGD("OKP fallback to L3");
security_level = kSecurityLevelL3;
}
DeviceFiles handle(file_system_);
if (!handle.Init(security_level)) {
LOGE("Unable to initialize device files");
return CdmResponseType(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 CdmResponseType(LIST_USAGE_ERROR_2);
}
return CdmResponseType(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));
if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) {
LOGD("OKP fallback to L3");
security_level = kSecurityLevelL3;
}
DeviceFiles handle(file_system_);
if (!handle.Init(security_level)) {
LOGE("Unable to initialize device files");
return CdmResponseType(DELETE_USAGE_ERROR_1);
}
std::string provider_session_token;
if (!handle.GetProviderSessionToken(app_id, key_set_id,
&provider_session_token)) {
LOGE("GetProviderSessionToken failed");
return CdmResponseType(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) {
if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) {
LOGD("OKP fallback to L3");
security_level = kSecurityLevelL3;
}
DeviceFiles handle(file_system_);
if (!handle.Init(security_level)) {
LOGE("Cannot initialize device files");
return CdmResponseType(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 CdmResponseType(GET_OFFLINE_LICENSE_STATE_ERROR_2);
}
*license_state = license_data.state;
return CdmResponseType(NO_ERROR);
}
CdmResponseType CdmEngine::RemoveOfflineLicense(
const CdmKeySetId& key_set_id, CdmSecurityLevel security_level) {
if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) {
LOGD("OKP fallback to L3");
security_level = kSecurityLevelL3;
}
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 CdmResponseType(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 = CdmResponseType(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 = CdmResponseType(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) {
// Try to find usage info at the default security level. If the
// security level is unprovisioned or we are unable to find it,
// try L3.
CdmResponseType status =
GetUsageInfo(app_id, ssid, kLevelDefault, error_detail, usage_info);
switch (status.Enum()) {
case NEED_PROVISIONING:
case GET_USAGE_INFO_ERROR_1:
case GET_USAGE_INFO_ERROR_2:
case USAGE_INFO_NOT_FOUND:
status = GetUsageInfo(app_id, ssid, kLevel3, error_detail, usage_info);
return status;
default:
return status;
}
}
CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id,
const CdmSecureStopId& ssid,
RequestedSecurityLevel security_level,
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 CdmResponseType(PARAMETER_NULL);
}
usage_property_set_->set_security_level(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: status = %d", static_cast<int>(status));
return status;
}
DeviceFiles handle(file_system_);
if (!handle.Init(usage_session_->GetSecurityLevel())) {
LOGE("Device file init error");
return CdmResponseType(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 CdmResponseType(GET_USAGE_INFO_ERROR_2);
}
if (!handle.RetrieveUsageInfo(DeviceFiles::GetUsageInfoFileName(app_id),
ssid, &usage_data)) {
// No entry found for that ssid.
return CdmResponseType(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 CdmResponseType(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 CdmResponseType(PARAMETER_NULL);
}
// Return a random usage report from a random security level
RequestedSecurityLevel security_level =
wvutil::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 CdmResponseType(
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, RequestedSecurityLevel requested_security_level,
int* error_detail, CdmUsageInfo* usage_info) {
LOGI("app_id = %s, security_level = %s", IdToString(app_id),
RequestedSecurityLevelToString(requested_security_level));
if (usage_info == nullptr) {
LOGE("Output |usage_info| is null");
return CdmResponseType(PARAMETER_NULL);
}
if (requested_security_level == kLevelDefault && OkpIsInFallbackMode()) {
LOGD("OKP fallback to L3");
requested_security_level = kLevel3;
}
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 CdmResponseType(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 CdmResponseType(GET_USAGE_INFO_ERROR_4);
}
if (usage_data.empty()) {
usage_info->clear();
return CdmResponseType(NO_ERROR);
}
const size_t index = wvutil::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.Enum()) {
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 RequestedSecurityLevel 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_->SupportsUsageTable()) {
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_index);
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 = CdmResponseType(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))) {
RequestedSecurityLevel 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;
UsageEntry usage_entry;
UsageEntryIndex usage_entry_index;
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_index, &drm_certificate, &wrapped_private_key)) {
// Try other security level
continue;
}
if (usage_session_->SupportsUsageTable()) {
status = usage_session_->DeleteUsageEntry(usage_entry_index);
if (!handle.DeleteUsageInfo(DeviceFiles::GetUsageInfoFileName(app_id),
provider_session_token)) {
status = CdmResponseType(REMOVE_USAGE_INFO_ERROR_1);
}
usage_session_.reset();
return status;
}
} else {
LOGE("Failed to initialize L%d device files", j);
status = CdmResponseType(REMOVE_USAGE_INFO_ERROR_2);
}
}
usage_session_.reset();
return CdmResponseType(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 CdmResponseType(RELEASE_USAGE_INFO_ERROR);
}
const CdmResponseType status = usage_session_->ReleaseKey(message);
usage_session_.reset();
if (NO_ERROR != status) {
LOGE("ReleaseKey failed: status = %d", static_cast<int>(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 CdmResponseType(EMPTY_KEYSET_ID_ENG_5);
}
if (release_message == nullptr) {
LOGE("Output |release_message| is null");
return CdmResponseType(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 CdmResponseType(SESSION_NOT_FOUND_11);
}
DeviceFiles handle(file_system_);
if (!handle.Init(session->GetSecurityLevel())) {
LOGE("Unable to initialize device files");
return CdmResponseType(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_index), &(usage_data.drm_certificate),
&(usage_data.wrapped_private_key))) {
LOGE("Unable to find usage information");
return CdmResponseType(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.Enum()) {
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 CdmResponseType(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 CdmResponseType(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 CdmResponseType(SESSION_NOT_FOUND_FOR_DECRYPT);
}
} else {
if (!session_map_.FindSession(session_id, &session)) {
LOGE("Session not found: session_id = %s", IdToString(session_id));
return CdmResponseType(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 CdmResponseType(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 CdmResponseType(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 CdmResponseType(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 CdmResponseType(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 CdmResponseType(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 CdmResponseType(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 CdmResponseType(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 CdmResponseType(PARAMETER_NULL);
}
if (frame_number == nullptr) {
LOGE("Output |frame_number| is null");
return CdmResponseType(PARAMETER_NULL);
}
if (hash == nullptr) {
LOGE("Output |hash| is null");
return CdmResponseType(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 CdmResponseType(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 CdmResponseType(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 CdmResponseType(INVALID_DECRYPT_HASH_FORMAT);
}
*hash = wvutil::a2bs_hex(tokens[2]);
if (hash->empty()) {
LOGE("Malformed hash: %s", hash_string.c_str());
return CdmResponseType(INVALID_DECRYPT_HASH_FORMAT);
}
return CdmResponseType(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 CdmResponseType(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 CdmResponseType(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() {
wvutil::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)->SupportsUsageTable() &&
(*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 CdmResponseType(SESSION_NOT_FOUND_23);
}
session->GetMetrics()->playback_id_.Record(playback_id);
return CdmResponseType(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);
}
}
bool CdmEngine::OkpCheck() {
std::unique_lock<std::mutex> lock(okp_mutex_);
if (okp_initialized_) {
return static_cast<bool>(okp_provisioner_);
}
okp_initialized_ = true;
// Creating a CryptoSession will initialize OEMCrypto and flag the need
// for OKP.
std::unique_ptr<CryptoSession> crypto_session(
CryptoSession::MakeCryptoSession(metrics_->GetCryptoMetrics()));
if (!crypto_session->needs_keybox_provisioning()) {
// System does not require OKP provisioning.
return false;
}
okp_provisioner_ = OtaKeyboxProvisioner::Create(metrics_->GetCryptoMetrics());
if (!okp_provisioner_) {
LOGE("Failed to create engine OKP handler, falling back to L3");
okp_fallback_ = true;
return false;
}
if (okp_provisioner_->IsProvisioned()) {
// This should have been caught by call to needs_keybox_provisioning(),
// but possible with simultaneous apps.
okp_provisioner_.reset();
return false;
}
if (okp_provisioner_->IsInFallbackMode()) {
LOGD("Engine is in OKP fallback mode");
okp_fallback_ = true;
okp_provisioner_.reset();
return false;
}
return true;
}
bool CdmEngine::OkpIsInFallbackMode() {
const bool check = OkpCheck();
std::unique_lock<std::mutex> lock(okp_mutex_);
if (!check || !okp_provisioner_ || okp_fallback_) {
return okp_fallback_;
}
if (!okp_provisioner_->IsInFallbackMode()) {
return false;
}
// Trigger fallback.
LOGD("Engine is entering OKP fallback mode");
okp_provisioner_.reset();
okp_fallback_ = true;
return true;
}
void CdmEngine::OkpTriggerFallback() {
std::unique_lock<std::mutex> lock(okp_mutex_);
if (!okp_initialized_) {
LOGD("Call to OKP fallback before OKP setup");
return;
}
if (okp_fallback_) return;
LOGD("Engine is entering OKP fallback mode");
okp_provisioner_.reset();
okp_fallback_ = true;
}
void CdmEngine::OkpCleanUp() {
std::unique_lock<std::mutex> lock(okp_mutex_);
if (!okp_initialized_) {
LOGD("Call to OKP fallback before OKP setup");
return;
}
okp_provisioner_.reset();
}
void CdmEngine::SetDefaultOtaKeyboxFallbackDurationRules() {
OkpCheck();
std::unique_lock<std::mutex> lock(okp_mutex_);
auto* system_fallback_policy = CryptoSession::GetOkpFallbackPolicy();
if (!system_fallback_policy) {
LOGW("No system fallback policy available");
return;
}
system_fallback_policy->SetDefaultBackoffDurationRules();
}
void CdmEngine::SetFastOtaKeyboxFallbackDurationRules() {
OkpCheck();
std::unique_lock<std::mutex> lock(okp_mutex_);
auto* system_fallback_policy = CryptoSession::GetOkpFallbackPolicy();
if (!system_fallback_policy) {
LOGW("No system fallback policy available");
return;
}
system_fallback_policy->SetFastBackoffDurationRules();
}
} // namespace wvcdm