Files
android/libwvdrmengine/cdm/core/src/cdm_engine.cpp
Cong Lin 30669a7b67 Cdm to expose GetDeviceInformation() and GetDeviceSignedCsrPayload()
GetDeviceInformation() and GetDeviceSignedCsrPayload() are added to
cdm_engine and crypto_session, so that they can be queried by DRM
plugin. This is to allow the wv drm HAL to be able to extract BCC and
CSR payload to build CSR for prov 4 device registration, such that we
don't need a separate RKP HAL to do this job.

Changes to the DRM plugin to use the exposed methods will be in the
coming CL.

Bug: 286556950
Test: request_license_test
Merged from https://widevine-internal-review.googlesource.com/178890

Merged from https://widevine-internal-review.googlesource.com/179730

Change-Id: Ibafa3a58c99fbb8f1f25f8951d3749110bd32176
2024-01-30 16:09:41 -08:00

2440 lines
87 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
std::string 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;
// V17 and forward.
case HDCP_V1_0:
return QUERY_VALUE_HDCP_V1_0;
case HDCP_V1_1:
return QUERY_VALUE_HDCP_V1_1;
case HDCP_V1_2:
return QUERY_VALUE_HDCP_V1_2;
case HDCP_V1_3:
return QUERY_VALUE_HDCP_V1_3;
case HDCP_V1_4:
return QUERY_VALUE_HDCP_V1_4;
case HDCP_NO_DIGITAL_OUTPUT:
return QUERY_VALUE_HDCP_NO_DIGITAL_OUTPUT;
}
return QUERY_VALUE_HDCP_LEVEL_UNKNOWN;
}
} // 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), file_system_(file_system), spoid_(EMPTY_SPOID) {
assert(file_system);
Properties::Init();
}
CdmEngine::~CdmEngine() {
{
std::unique_lock<std::mutex> lock(usage_session_mutex_);
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.code()) {
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);
}
if (query_token == QUERY_KEY_DEBUG_BOOT_CERTIFICATE_CHAIN) {
std::string bcc;
std::string signature_unused;
const CdmResponseType status = crypto_session->GetBootCertificateChain(
security_level, &bcc, &signature_unused);
if (status == NO_ERROR) {
LOGV("BCC length: %zu", bcc.size());
*query_response = std::move(bcc);
return CdmResponseType(NO_ERROR);
}
if (status == NOT_IMPLEMENTED_ERROR ||
status == PROVISIONING_TYPE_IS_NOT_BOOT_CERTIFICATE_CHAIN_ERROR) {
LOGD("BCC not available: %d", status.ToInt());
*query_response = QUERY_VALUE_NONE;
return CdmResponseType(NO_ERROR);
}
LOGE("Failed to extract BCC: status = %d", status.ToInt());
return status;
}
if (query_token == QUERY_KEY_DEVICE_INFORMATION) {
std::string device_info;
const CdmResponseType status =
crypto_session->GetDeviceInformation(security_level, &device_info);
if (status == NO_ERROR) {
LOGV("device_info length: %zu", device_info.size());
*query_response = std::move(device_info);
return CdmResponseType(NO_ERROR);
}
if (status == NOT_IMPLEMENTED_ERROR ||
status == PROVISIONING_TYPE_IS_NOT_BOOT_CERTIFICATE_CHAIN_ERROR) {
LOGV("device_info not available: %s", status.ToString().c_str());
*query_response = QUERY_VALUE_NONE;
return CdmResponseType(NO_ERROR);
}
LOGE("Failed to extract device_info: %s", status.ToString().c_str());
return status;
}
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);
}
CdmResponseType CdmEngine::QueryDeviceSignedCsrPayload(
const std::string& challenge, const std::string& device_info,
std::string* query_response) {
if (query_response == nullptr) {
LOGE("Output |query_response| is null");
return CdmResponseType(PARAMETER_NULL);
}
std::unique_ptr<CryptoSession> crypto_session(
CryptoSession::MakeCryptoSession(metrics_->GetCryptoMetrics()));
std::string signed_csr_payload;
const CdmResponseType status = crypto_session->GetDeviceSignedCsrPayload(
kLevelDefault, challenge, device_info, &signed_csr_payload);
if (status == NO_ERROR) {
LOGV("signed_csr_payload length: %zu", signed_csr_payload.size());
*query_response = std::move(signed_csr_payload);
return CdmResponseType(NO_ERROR);
}
if (status == NOT_IMPLEMENTED_ERROR ||
status == PROVISIONING_TYPE_IS_NOT_BOOT_CERTIFICATE_CHAIN_ERROR) {
LOGD("signed_csr_payload not available: %s", status.ToString().c_str());
*query_response = QUERY_VALUE_NONE;
return CdmResponseType(NO_ERROR);
}
LOGE("Failed to extract signed_csr_payload: %s", status.ToString().c_str());
return status;
}
// 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.
std::unique_lock<std::mutex> cert_lock(cert_provisioning_mutex_);
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));
std::unique_lock<std::mutex> cert_lock(cert_provisioning_mutex_);
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<CdmKeySetId>* ksids,
std::vector<CdmSecureStopId>* psts) {
if (!ksids && !psts) {
LOGE("Outputs |ksids| and |psts| 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, psts)) {
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 CdmKeySetId& 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::StoreAtscLicense(
RequestedSecurityLevel requested_security_level,
const CdmKeySetId& key_set_id, const std::string& serialized_license_data) {
std::string security_level_string;
CdmResponseType status =
QueryStatus(requested_security_level, QUERY_KEY_SECURITY_LEVEL,
&security_level_string);
if (status != NO_ERROR) return status;
DeviceFiles handle(file_system_);
CdmSecurityLevel security_level =
security_level_string == QUERY_VALUE_SECURITY_LEVEL_L1 ? kSecurityLevelL1
: kSecurityLevelL3;
if (!handle.Init(security_level)) {
LOGE("Unable to initialize device files");
return CdmResponseType(STORE_ATSC_LICENSE_DEVICE_FILES_INIT_ERROR);
}
DeviceFiles::ResponseType response_type =
handle.StoreAtscLicense(key_set_id, serialized_license_data);
if (response_type != DeviceFiles::kNoError) {
LOGE("Unable to store ATSC license: response = %s",
DeviceFiles::ResponseTypeToString(response_type));
return CdmResponseType(STORE_ATSC_LICENSE_ERROR);
}
return CdmResponseType(NO_ERROR);
}
CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id,
const CdmSecureStopId& ssid,
int* error_detail,
CdmUsageReport* usage_report) {
// 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_report);
switch (status.code()) {
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_report);
return status;
default:
return status;
}
}
CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id,
const CdmSecureStopId& ssid,
RequestedSecurityLevel security_level,
int* error_detail,
CdmUsageReport* usage_report) {
LOGI("app_id = %s, ssid = %s", IdToString(app_id), IdToString(ssid));
std::unique_lock<std::mutex> lock(usage_session_mutex_);
if (!usage_property_set_) {
usage_property_set_.reset(new UsagePropertySet());
}
if (usage_report == nullptr) {
LOGE("Output |usage_report| is null");
return CdmResponseType(PARAMETER_NULL);
}
usage_report->clear();
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));
return status;
}
CdmKeyRequest request;
status = usage_session_->GenerateReleaseRequest(&request);
if (KEY_MESSAGE != status) {
LOGE("GenerateReleaseRequest failed: status = %d",
static_cast<int>(status));
return status;
}
*usage_report = std::move(request.message);
return CdmResponseType(KEY_MESSAGE);
}
CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id,
int* error_detail,
CdmUsageReport* usage_report) {
LOGI("app_id = %s", IdToString(app_id));
if (usage_report == nullptr) {
LOGE("Output |usage_report| 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_report);
if (KEY_MESSAGE == status && !usage_report->empty()) {
return status;
}
} while (KEY_CANCELED == status);
security_level = (kLevel3 == security_level) ? kLevelDefault : kLevel3;
do {
status = GetUsageInfo(app_id, security_level, error_detail, usage_report);
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, CdmUsageReport* usage_report) {
LOGI("app_id = %s, security_level = %s", IdToString(app_id),
RequestedSecurityLevelToString(requested_security_level));
if (usage_report == nullptr) {
LOGE("Output |usage_report| is null");
return CdmResponseType(PARAMETER_NULL);
}
usage_report->clear();
if (requested_security_level == kLevelDefault && OkpIsInFallbackMode()) {
LOGD("OKP fallback to L3");
requested_security_level = kLevel3;
}
std::unique_lock<std::mutex> lock(usage_session_mutex_);
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()) {
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));
return status;
}
CdmKeyRequest request;
status = usage_session_->GenerateReleaseRequest(&request);
switch (status.code()) {
case KEY_MESSAGE:
*usage_report = std::move(request.message);
break;
case KEY_CANCELED: // usage information not present in
usage_session_->DeleteLicenseFile(); // OEMCrypto, delete and try again
break;
default:
LOGE("GenerateReleaseRequest failed: status = %d",
static_cast<int>(status));
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));
std::unique_lock<std::mutex> lock(usage_session_mutex_);
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].key_set_id)) {
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));
std::unique_lock<std::mutex> lock(usage_session_mutex_);
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());
DeviceFiles::CdmUsageData usage_data;
if (!handle.RetrieveUsageInfo(DeviceFiles::GetUsageInfoFileName(app_id),
provider_session_token, &usage_data)) {
// Try other security level
continue;
}
if (usage_session_->SupportsUsageTable()) {
status = usage_session_->DeleteUsageEntry(usage_data.usage_entry_index);
if (!handle.DeleteUsageInfo(DeviceFiles::GetUsageInfoFileName(app_id),
usage_data.key_set_id)) {
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 CdmKeyResponse& message) {
LOGI("message_size = %zu", message.size());
std::unique_lock<std::mutex> lock(usage_session_mutex_);
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.code()) {
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(rfrias) 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);
}
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();
}
CdmResponseType CdmEngine::SignRsa(const std::string& wrapped_key,
const std::string& message,
std::string* signature,
RSA_Padding_Scheme padding_scheme) {
// Try to open cdm session.
CdmSessionId session_id;
auto sts = OpenSession("com.widevine", nullptr, nullptr, &session_id);
if (sts != NO_ERROR) {
LOGE("OpenSession failed, status: %d", static_cast<int>(sts));
return sts;
}
// Retrieve the cdm session
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));
CloseSession(session_id);
return CdmResponseType(SESSION_NOT_FOUND_24);
}
}
// Load cast private key for signing
CryptoWrappedKey key(CryptoWrappedKey::kRsa, wrapped_key);
sts = session->LoadCastPrivateKey(key);
if (sts != NO_ERROR) {
LOGE("LoadCastPrivateKey failed, status: %d", static_cast<int>(sts));
CloseSession(session_id);
return sts;
}
// Generate Rsa signature for cast message
sts = session->GenerateRsaSignature(message, signature, padding_scheme);
if (sts != NO_ERROR) {
LOGE("GenerateRsaSignature failed, status: %d", static_cast<int>(sts));
CloseSession(session_id);
return sts;
}
// Try to close cdm session.
sts = CloseSession(session_id);
if (sts != NO_ERROR) {
LOGE("CloseSession failed, status: %d", static_cast<int>(sts));
return sts;
}
return sts;
}
} // namespace wvcdm