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
2440 lines
87 KiB
C++
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, ¤t_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(¤t_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
|