[ Cherry pick of http://ag/15836995 ] [ Merge of http://go/wvgerrit/133744 ] This changes adds several small classes which contain and manage system and engine information related to OTA keybox provisioning. These classes closely map to the OKP device file messages. Bug: 189232882 Test: Linux unit tests Change-Id: Ia9334c38f9d7ea89b30d9ad05f0595570bb38658 Storing and loading OKP info. [ Merge of http://go/wvgerrit/133763 and http://go/ag/15645333 ] This change extends the DeviceFiles module to be able to store and load OKP info. Mild data validation is performed when storing and loading the information. Bug: 189232882 Test: Android unit tests Change-Id: I077de3234157252f2255a4389bf82a8d5344a355 System OKP fallback policy. [ Merge of http://go/wvgerrit/133783 and http://go/ag/15645334 ] SystemFallbackPolicy provides a thread-safe interface for accessing and modifying OKP info. Bug: 189232882 Test: Android unit tests Change-Id: I4e43e3bc047ed5fb6cb517b53e4094e812b70e1e Engine OKP provisioner. [ Merge of http://go/wvgerrit/133803 and http://go/ag/15645335 ] The OtaKeyboxProvisioner provides a CdmEngine-specific context for performing OTA keybox provisioning. Utilizes the system-wide SystemFallbackPolicy to relay provisioning status between engines. The provisioner will handle message wrapping and unwrapping of the raw OTA keybox request / response into the SignedProvisioningMessage which is sent to/received from the provisioning server. [ Partial merge of http://go/wvgerrit/125844 ] Note: Includes partial CryptoSession changes from various CLs. CryptoSession functionality has been stripped to reduce impact of this CL. Bug: 189232882 Test: Android unit tests Change-Id: I282bf7d1887daefb2250af1bd595c4dc3dfcfb29 Integrated OKP into CDM Engine [ Merge of http://go/wvgerrit/133804 and http://go/ag/15646376 ] Extended the functionality of the CdmEngine to check if the device requires OKP and to initialize OKP resources if required. The functionality of OpenSession() and GetProvisioningRequest() have been the most affected. If OKP is required, these methods will signal to the app that provisioning is required and will return an OKP request. Once a device is provisioned, the OKP data is cleared away and the CdmEngine will resume normal operation. Engines created after a device is provisioned will immediately enter normal operations. The exception is for CdmEngines which failed to perform OKP for some reason and are still running. Those apps will need to restart before gaining access to L1 operations. Bug: 187646550 Test: Android integration tests Change-Id: Ia572a66a7b73479355758aa3d0c682691eaca0fc
2158 lines
74 KiB
C++
2158 lines
74 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 "ota_keybox_provisioner.h"
|
|
#include "properties.h"
|
|
#include "string_conversions.h"
|
|
#include "wv_cdm_constants.h"
|
|
#include "wv_cdm_event_listener.h"
|
|
|
|
namespace wvcdm {
|
|
namespace {
|
|
const uint64_t kReleaseSessionTimeToLive = 60; // seconds
|
|
const uint32_t kUpdateUsageInformationPeriod = 60; // seconds
|
|
} // namespace
|
|
|
|
class UsagePropertySet : public CdmClientPropertySet {
|
|
public:
|
|
UsagePropertySet() {}
|
|
~UsagePropertySet() override {}
|
|
void set_security_level(SecurityLevel security_level) {
|
|
if (kLevel3 == security_level)
|
|
security_level_ = QUERY_VALUE_SECURITY_LEVEL_L3;
|
|
else
|
|
security_level_.clear();
|
|
}
|
|
const std::string& security_level() const override { return security_level_; }
|
|
bool use_privacy_mode() const override { return false; }
|
|
const std::string& service_certificate() const override { return empty_; }
|
|
void set_service_certificate(const std::string&) override {}
|
|
bool is_session_sharing_enabled() const override { return false; }
|
|
uint32_t session_sharing_id() const override { return 0; }
|
|
void set_session_sharing_id(uint32_t /* id */) override {}
|
|
const std::string& app_id() const override { return app_id_; }
|
|
void set_app_id(const std::string& appId) { app_id_ = appId; }
|
|
bool use_atsc_mode() const override { return false; }
|
|
|
|
private:
|
|
std::string app_id_;
|
|
std::string security_level_;
|
|
const std::string empty_;
|
|
};
|
|
|
|
CdmEngine::CdmEngine(FileSystem* file_system,
|
|
std::shared_ptr<metrics::EngineMetrics> metrics)
|
|
: metrics_(metrics),
|
|
cert_provisioning_(),
|
|
file_system_(file_system),
|
|
spoid_(EMPTY_SPOID),
|
|
usage_session_(),
|
|
usage_property_set_(),
|
|
last_usage_information_update_time_(0) {
|
|
assert(file_system);
|
|
Properties::Init();
|
|
}
|
|
|
|
CdmEngine::~CdmEngine() {
|
|
usage_session_.reset();
|
|
std::unique_lock<std::recursive_mutex> lock(session_map_lock_);
|
|
session_map_.Terminate();
|
|
}
|
|
|
|
CdmResponseType CdmEngine::OpenSession(const CdmKeySystem& key_system,
|
|
CdmClientPropertySet* property_set,
|
|
const CdmSessionId& forced_session_id,
|
|
WvCdmEventListener* event_listener) {
|
|
return OpenSession(key_system, property_set, event_listener,
|
|
&forced_session_id, nullptr);
|
|
}
|
|
|
|
CdmResponseType CdmEngine::OpenSession(const CdmKeySystem& key_system,
|
|
CdmClientPropertySet* property_set,
|
|
WvCdmEventListener* event_listener,
|
|
CdmSessionId* session_id) {
|
|
return OpenSession(key_system, property_set, event_listener, nullptr,
|
|
session_id);
|
|
}
|
|
|
|
CdmResponseType CdmEngine::OpenSession(const CdmKeySystem& key_system,
|
|
CdmClientPropertySet* property_set,
|
|
WvCdmEventListener* event_listener,
|
|
const CdmSessionId* forced_session_id,
|
|
CdmSessionId* session_id) {
|
|
if (!ValidateKeySystem(key_system)) {
|
|
LOGI("Invalid key system: %s", IdToString(key_system));
|
|
return INVALID_KEY_SYSTEM;
|
|
}
|
|
|
|
if (session_id == nullptr && forced_session_id == nullptr) {
|
|
LOGE("Input |forced_session_id| and output |session_id| are both null");
|
|
return PARAMETER_NULL;
|
|
}
|
|
|
|
if (forced_session_id != nullptr) {
|
|
if (forced_session_id->empty()) {
|
|
// This should be enforce by the CE CDM code.
|
|
return EMPTY_SESSION_ID;
|
|
}
|
|
if (session_map_.Exists(*forced_session_id)) {
|
|
return DUPLICATE_SESSION_ID_SPECIFIED;
|
|
}
|
|
LOGD("forced_session_id = %s", IdPtrToString(forced_session_id));
|
|
}
|
|
bool forced_level3 = false;
|
|
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 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;
|
|
}
|
|
if (sts != NO_ERROR) {
|
|
LOGE("Bad session init: status = %d", static_cast<int>(sts));
|
|
return sts;
|
|
}
|
|
|
|
const CdmSessionId id = new_session->session_id();
|
|
LOGI("New session: session_id = %s", IdToString(id));
|
|
|
|
std::unique_lock<std::recursive_mutex> lock(session_map_lock_);
|
|
session_map_.Add(id, new_session.release());
|
|
if (session_id) *session_id = id;
|
|
return NO_ERROR;
|
|
}
|
|
|
|
CdmResponseType CdmEngine::OpenKeySetSession(
|
|
const CdmKeySetId& key_set_id, CdmClientPropertySet* property_set,
|
|
WvCdmEventListener* event_listener) {
|
|
LOGI("key_set_id = %s", IdToString(key_set_id));
|
|
if (key_set_id.empty()) {
|
|
LOGE("Invalid key set ID");
|
|
return EMPTY_KEYSET_ID_ENG_1;
|
|
}
|
|
|
|
// If in-use, release key set before re-opening, to avoid leaking
|
|
// resources (CryptoSession etc).
|
|
bool key_set_in_use = false;
|
|
{
|
|
std::unique_lock<std::mutex> lock(release_key_sets_lock_);
|
|
key_set_in_use =
|
|
release_key_sets_.find(key_set_id) != release_key_sets_.end();
|
|
}
|
|
if (key_set_in_use) {
|
|
LOGD("Reopening existing key session");
|
|
CloseKeySetSession(key_set_id);
|
|
}
|
|
|
|
CdmSessionId session_id;
|
|
const CdmResponseType sts =
|
|
OpenSession(KEY_SYSTEM, property_set, event_listener,
|
|
nullptr /* forced_session_id */, &session_id);
|
|
|
|
if (sts != NO_ERROR) return sts;
|
|
|
|
std::unique_lock<std::mutex> lock(release_key_sets_lock_);
|
|
release_key_sets_[key_set_id] = std::make_pair(
|
|
session_id, clock_.GetCurrentTime() + kReleaseSessionTimeToLive);
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
CdmResponseType CdmEngine::CloseSession(const CdmSessionId& session_id) {
|
|
LOGI("session_id = %s", IdToString(session_id));
|
|
std::unique_lock<std::recursive_mutex> lock(session_map_lock_);
|
|
if (!session_map_.CloseSession(session_id)) {
|
|
LOGE("Session not found: session_id = %s", IdToString(session_id));
|
|
return SESSION_NOT_FOUND_1;
|
|
}
|
|
metrics_->ConsolidateSessions();
|
|
return NO_ERROR;
|
|
}
|
|
|
|
CdmResponseType CdmEngine::CloseKeySetSession(const CdmKeySetId& key_set_id) {
|
|
LOGI("key_set_id = %s", IdToString(key_set_id));
|
|
CdmSessionId session_id;
|
|
{
|
|
std::unique_lock<std::mutex> lock(release_key_sets_lock_);
|
|
CdmReleaseKeySetMap::iterator iter = release_key_sets_.find(key_set_id);
|
|
if (iter == release_key_sets_.end()) {
|
|
LOGE("Key set not found: key_set_id = %s", IdToString(key_set_id));
|
|
return KEYSET_ID_NOT_FOUND_1;
|
|
}
|
|
session_id = iter->second.first;
|
|
}
|
|
const CdmResponseType sts = CloseSession(session_id);
|
|
std::unique_lock<std::mutex> lock(release_key_sets_lock_);
|
|
CdmReleaseKeySetMap::iterator iter = release_key_sets_.find(key_set_id);
|
|
if (iter != release_key_sets_.end()) {
|
|
release_key_sets_.erase(iter);
|
|
}
|
|
return sts;
|
|
}
|
|
|
|
bool CdmEngine::IsOpenSession(const CdmSessionId& session_id) {
|
|
std::unique_lock<std::recursive_mutex> lock(session_map_lock_);
|
|
return session_map_.Exists(session_id);
|
|
}
|
|
|
|
CdmResponseType CdmEngine::GenerateKeyRequest(
|
|
const CdmSessionId& session_id, const CdmKeySetId& key_set_id,
|
|
const InitializationData& init_data, const CdmLicenseType license_type,
|
|
CdmAppParameterMap& app_parameters, CdmKeyRequest* key_request) {
|
|
LOGI("session_id = %s, key_set_id = %s, license_type = %s",
|
|
IdToString(session_id), IdToString(key_set_id),
|
|
CdmLicenseTypeToString(license_type));
|
|
if (key_request == nullptr) {
|
|
LOGE("Output |key_request| is null");
|
|
return PARAMETER_NULL;
|
|
}
|
|
|
|
CdmSessionId id = session_id;
|
|
// NOTE: If AlwaysUseKeySetIds() is true, there is no need to consult the
|
|
// |release_key_sets_| map for release licenses.
|
|
if (license_type == kLicenseTypeRelease &&
|
|
!Properties::AlwaysUseKeySetIds()) {
|
|
if (key_set_id.empty()) {
|
|
LOGE("Invalid key set ID");
|
|
return EMPTY_KEYSET_ID_ENG_2;
|
|
}
|
|
if (!session_id.empty()) {
|
|
LOGE("Session ID should be empty: session_id = %s",
|
|
IdToString(session_id));
|
|
return INVALID_SESSION_ID;
|
|
}
|
|
|
|
std::unique_lock<std::mutex> lock(release_key_sets_lock_);
|
|
CdmReleaseKeySetMap::iterator iter = release_key_sets_.find(key_set_id);
|
|
if (iter == release_key_sets_.end()) {
|
|
LOGE("Key set not found: key_set_id = %s", IdToString(key_set_id));
|
|
return KEYSET_ID_NOT_FOUND_2;
|
|
}
|
|
|
|
id = iter->second.first;
|
|
}
|
|
|
|
std::shared_ptr<CdmSession> session;
|
|
if (!session_map_.FindSession(id, &session)) {
|
|
LOGE("Session not found: session_id = %s", IdToString(id));
|
|
return SESSION_NOT_FOUND_2;
|
|
}
|
|
|
|
key_request->message.clear();
|
|
|
|
if (license_type == kLicenseTypeRelease && !session->license_received()) {
|
|
int error_detail = NO_ERROR;
|
|
const CdmResponseType restore_status = session->RestoreOfflineSession(
|
|
key_set_id, kLicenseTypeRelease, &error_detail);
|
|
session->GetMetrics()->cdm_session_restore_offline_session_.Increment(
|
|
restore_status, error_detail);
|
|
if (restore_status != KEY_ADDED) {
|
|
LOGE("Key release restoration failed: session_id = %s, status = %d",
|
|
IdToString(id), static_cast<int>(restore_status));
|
|
return restore_status;
|
|
}
|
|
}
|
|
|
|
const CdmResponseType sts = session->GenerateKeyRequest(
|
|
init_data, license_type, app_parameters, key_request);
|
|
|
|
if (KEY_ADDED == sts) {
|
|
return sts;
|
|
}
|
|
if (KEY_MESSAGE != sts) {
|
|
LOGE("CdmSession::GenerateKeyRequest failed: session_id = %s, status = %d",
|
|
IdToString(id), static_cast<int>(sts));
|
|
return sts;
|
|
}
|
|
|
|
if (license_type == kLicenseTypeRelease) {
|
|
OnKeyReleaseEvent(key_set_id);
|
|
}
|
|
|
|
LOGD("key_request = (%zu) %s", key_request->message.size(),
|
|
wvcdm::Base64SafeEncode(key_request->message).c_str());
|
|
|
|
return KEY_MESSAGE;
|
|
}
|
|
|
|
CdmResponseType CdmEngine::AddKey(const CdmSessionId& session_id,
|
|
const CdmKeyResponse& key_data,
|
|
CdmLicenseType* license_type,
|
|
CdmKeySetId* key_set_id) {
|
|
LOGI("session_id = %s, key_set_id = %s", IdToString(session_id),
|
|
IdPtrToString(key_set_id));
|
|
if (license_type == nullptr) {
|
|
LOGE("Output |license_type| is null");
|
|
return PARAMETER_NULL;
|
|
}
|
|
|
|
CdmSessionId id = session_id;
|
|
const bool license_type_release = session_id.empty();
|
|
if (license_type_release) {
|
|
if (key_set_id == nullptr) {
|
|
LOGE("Input/output |key_set_id| is null");
|
|
return PARAMETER_NULL;
|
|
}
|
|
|
|
if (key_set_id->empty()) {
|
|
LOGE("Invalid key set ID");
|
|
return EMPTY_KEYSET_ID_ENG_3;
|
|
}
|
|
|
|
std::unique_lock<std::mutex> lock(release_key_sets_lock_);
|
|
CdmReleaseKeySetMap::iterator iter = release_key_sets_.find(*key_set_id);
|
|
if (iter == release_key_sets_.end()) {
|
|
LOGE("Key set not found: key_set_id = %s", IdPtrToString(key_set_id));
|
|
return KEYSET_ID_NOT_FOUND_3;
|
|
}
|
|
id = iter->second.first;
|
|
} else {
|
|
LOGD("key_data = (%zu) %s", key_data.size(),
|
|
wvcdm::Base64SafeEncode(key_data).c_str());
|
|
}
|
|
|
|
std::shared_ptr<CdmSession> session;
|
|
if (!session_map_.FindSession(id, &session)) {
|
|
LOGE("Session not found: session_id = %s", IdToString(id));
|
|
return SESSION_NOT_FOUND_3;
|
|
}
|
|
|
|
if (key_data.empty()) {
|
|
LOGE("No key data");
|
|
return EMPTY_KEY_DATA_1;
|
|
}
|
|
|
|
const CdmResponseType sts = session->AddKey(key_data);
|
|
if (sts == KEY_ADDED) {
|
|
if (session->is_release()) {
|
|
*license_type = kLicenseTypeRelease;
|
|
} else if (session->is_temporary()) {
|
|
*license_type = kLicenseTypeTemporary;
|
|
} else if (session->is_offline()) {
|
|
*license_type = kLicenseTypeOffline;
|
|
} else {
|
|
*license_type = kLicenseTypeStreaming;
|
|
}
|
|
}
|
|
|
|
if (key_set_id != nullptr) {
|
|
if ((session->is_offline() || session->has_provider_session_token()) &&
|
|
!license_type_release) {
|
|
*key_set_id = session->key_set_id();
|
|
LOGI("key_set_id = %s", IdPtrToString(key_set_id));
|
|
} else {
|
|
key_set_id->clear();
|
|
}
|
|
}
|
|
|
|
switch (sts) {
|
|
case KEY_ADDED:
|
|
break;
|
|
case NEED_KEY:
|
|
LOGI("Service certificate loaded, no key added: session_id = %s",
|
|
IdToString(id));
|
|
break;
|
|
default:
|
|
LOGE("CdmSession::AddKey failed: session_id = %s, status = %d",
|
|
IdToString(id), static_cast<int>(sts));
|
|
break;
|
|
}
|
|
return sts;
|
|
}
|
|
|
|
CdmResponseType CdmEngine::RestoreKey(const CdmSessionId& session_id,
|
|
const CdmKeySetId& key_set_id) {
|
|
LOGI("session_id = %s, key_set_id = %s", IdToString(session_id),
|
|
IdToString(key_set_id));
|
|
if (key_set_id.empty()) {
|
|
LOGI("Invalid key set ID");
|
|
return EMPTY_KEYSET_ID_ENG_4;
|
|
}
|
|
|
|
std::shared_ptr<CdmSession> session;
|
|
if (!session_map_.FindSession(session_id, &session)) {
|
|
LOGE("Session not found: session_id = %s", IdToString(session_id));
|
|
return SESSION_NOT_FOUND_4;
|
|
}
|
|
|
|
int error_detail = NO_ERROR;
|
|
const CdmResponseType sts = session->RestoreOfflineSession(
|
|
key_set_id, kLicenseTypeOffline, &error_detail);
|
|
session->GetMetrics()->cdm_session_restore_offline_session_.Increment(
|
|
sts, error_detail);
|
|
if (sts != KEY_ADDED && sts != GET_RELEASED_LICENSE_ERROR) {
|
|
LOGE("Restore offline session failed: session_id = %s, status = %d",
|
|
IdToString(session_id), static_cast<int>(sts));
|
|
}
|
|
return sts;
|
|
}
|
|
|
|
CdmResponseType CdmEngine::RemoveKeys(const CdmSessionId& session_id) {
|
|
LOGI("session_id = %s", IdToString(session_id));
|
|
std::shared_ptr<CdmSession> session;
|
|
if (!session_map_.FindSession(session_id, &session)) {
|
|
LOGE("Session not found: session_id = %s", IdToString(session_id));
|
|
return SESSION_NOT_FOUND_5;
|
|
}
|
|
session->RemoveKeys();
|
|
return NO_ERROR;
|
|
}
|
|
|
|
CdmResponseType CdmEngine::RemoveLicense(const CdmSessionId& session_id) {
|
|
LOGI("session_id = %s", IdToString(session_id));
|
|
std::shared_ptr<CdmSession> session;
|
|
if (!session_map_.FindSession(session_id, &session)) {
|
|
LOGE("Session not found: session_id = %s", IdToString(session_id));
|
|
return SESSION_NOT_FOUND_19;
|
|
}
|
|
return session->RemoveLicense();
|
|
}
|
|
|
|
CdmResponseType CdmEngine::GenerateRenewalRequest(
|
|
const CdmSessionId& session_id, CdmKeyRequest* key_request) {
|
|
LOGI("session_id = %s", IdToString(session_id));
|
|
if (key_request == nullptr) {
|
|
LOGE("Output |key_request| is null");
|
|
return PARAMETER_NULL;
|
|
}
|
|
std::shared_ptr<CdmSession> session;
|
|
if (!session_map_.FindSession(session_id, &session)) {
|
|
LOGE("Session not found: session_id = %s", IdToString(session_id));
|
|
return SESSION_NOT_FOUND_6;
|
|
}
|
|
key_request->message.clear();
|
|
const CdmResponseType sts = session->GenerateRenewalRequest(key_request);
|
|
if (KEY_MESSAGE != sts) {
|
|
LOGE("Failed: session_id = %s, status = %d", IdToString(session_id),
|
|
static_cast<int>(sts));
|
|
return sts;
|
|
}
|
|
return KEY_MESSAGE;
|
|
}
|
|
|
|
CdmResponseType CdmEngine::RenewKey(const CdmSessionId& session_id,
|
|
const CdmKeyResponse& key_data) {
|
|
LOGI("session_id = %s", IdToString(session_id));
|
|
if (key_data.empty()) {
|
|
LOGE("No key data");
|
|
return EMPTY_KEY_DATA_2;
|
|
}
|
|
std::shared_ptr<CdmSession> session;
|
|
if (!session_map_.FindSession(session_id, &session)) {
|
|
LOGE("Session not found: session_id = %s", IdToString(session_id));
|
|
return SESSION_NOT_FOUND_7;
|
|
}
|
|
CdmResponseType sts;
|
|
M_TIME(sts = session->RenewKey(key_data), session->GetMetrics(),
|
|
cdm_session_renew_key_, sts);
|
|
if (KEY_ADDED != sts) {
|
|
LOGE("Failed: session_id = %s, status = %d", IdToString(session_id),
|
|
static_cast<int>(sts));
|
|
return sts;
|
|
}
|
|
return KEY_ADDED;
|
|
}
|
|
|
|
CdmResponseType CdmEngine::SetSessionServiceCertificate(
|
|
const CdmSessionId& session_id, const std::string& service_certificate) {
|
|
LOGI("session_id = %s", IdToString(session_id));
|
|
std::shared_ptr<CdmSession> session;
|
|
if (!session_map_.FindSession(session_id, &session)) {
|
|
LOGE("Session not found: session_id = %s", IdToString(session_id));
|
|
return SESSION_NOT_FOUND_22;
|
|
}
|
|
return session->SetServiceCertificate(service_certificate);
|
|
}
|
|
|
|
CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level,
|
|
const std::string& query_token,
|
|
std::string* query_response) {
|
|
LOGD("security_level = %s, query_token = %s",
|
|
SecurityLevelToString(security_level), IdToString(query_token));
|
|
if (query_response == nullptr) {
|
|
LOGE("Output |query_response| is null");
|
|
return PARAMETER_NULL;
|
|
}
|
|
std::unique_ptr<CryptoSession> crypto_session(
|
|
CryptoSession::MakeCryptoSession(metrics_->GetCryptoMetrics()));
|
|
|
|
// 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 UNKNOWN_ERROR;
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
if (query_token == QUERY_KEY_CURRENT_HDCP_LEVEL ||
|
|
query_token == QUERY_KEY_MAX_HDCP_LEVEL) {
|
|
CryptoSession::HdcpCapability current_hdcp;
|
|
CryptoSession::HdcpCapability max_hdcp;
|
|
const CdmResponseType status = crypto_session->GetHdcpCapabilities(
|
|
security_level, ¤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 NO_ERROR;
|
|
}
|
|
if (query_token == QUERY_KEY_USAGE_SUPPORT) {
|
|
bool supports_usage_reporting;
|
|
const bool got_info = crypto_session->HasUsageInfoSupport(
|
|
security_level, &supports_usage_reporting);
|
|
|
|
if (!got_info) {
|
|
LOGW("HasUsageInfoSupport failed");
|
|
metrics_->GetCryptoMetrics()
|
|
->crypto_session_usage_information_support_.SetError(got_info);
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
metrics_->GetCryptoMetrics()
|
|
->crypto_session_usage_information_support_.Record(
|
|
supports_usage_reporting);
|
|
|
|
*query_response =
|
|
supports_usage_reporting ? QUERY_VALUE_TRUE : QUERY_VALUE_FALSE;
|
|
return NO_ERROR;
|
|
}
|
|
if (query_token == QUERY_KEY_NUMBER_OF_OPEN_SESSIONS) {
|
|
size_t number_of_open_sessions;
|
|
const CdmResponseType status = crypto_session->GetNumberOfOpenSessions(
|
|
security_level, &number_of_open_sessions);
|
|
if (status != NO_ERROR) {
|
|
LOGW("GetNumberOfOpenSessions failed: status = %d",
|
|
static_cast<int>(status));
|
|
return status;
|
|
}
|
|
*query_response = std::to_string(number_of_open_sessions);
|
|
return NO_ERROR;
|
|
}
|
|
if (query_token == QUERY_KEY_MAX_NUMBER_OF_SESSIONS) {
|
|
size_t maximum_number_of_sessions = 0;
|
|
const CdmResponseType status = crypto_session->GetMaxNumberOfSessions(
|
|
security_level, &maximum_number_of_sessions);
|
|
if (status != NO_ERROR) {
|
|
LOGW("GetMaxNumberOfOpenSessions failed: status = %d",
|
|
static_cast<int>(status));
|
|
return status;
|
|
}
|
|
*query_response = std::to_string(maximum_number_of_sessions);
|
|
return NO_ERROR;
|
|
}
|
|
if (query_token == QUERY_KEY_OEMCRYPTO_API_VERSION) {
|
|
uint32_t api_version;
|
|
if (!crypto_session->GetApiVersion(security_level, &api_version)) {
|
|
LOGW("GetApiVersion failed");
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
*query_response = std::to_string(api_version);
|
|
return NO_ERROR;
|
|
}
|
|
if (query_token == QUERY_KEY_CURRENT_SRM_VERSION) {
|
|
uint16_t current_srm_version;
|
|
const CdmResponseType status =
|
|
crypto_session->GetSrmVersion(¤t_srm_version);
|
|
if (status == NO_ERROR) {
|
|
*query_response = std::to_string(current_srm_version);
|
|
return NO_ERROR;
|
|
}
|
|
if (status == NO_SRM_VERSION) {
|
|
// SRM is not supported or not applicable (ex. local display only).
|
|
*query_response = QUERY_VALUE_NONE;
|
|
return NO_ERROR;
|
|
}
|
|
LOGW("GetCurrentSRMVersion failed: status = %d", static_cast<int>(status));
|
|
return status;
|
|
}
|
|
if (query_token == QUERY_KEY_SRM_UPDATE_SUPPORT) {
|
|
const bool is_srm_update_supported = crypto_session->IsSrmUpdateSupported();
|
|
*query_response =
|
|
is_srm_update_supported ? QUERY_VALUE_TRUE : QUERY_VALUE_FALSE;
|
|
return NO_ERROR;
|
|
}
|
|
if (query_token == QUERY_KEY_WVCDM_VERSION) {
|
|
std::string cdm_version;
|
|
if (!Properties::GetWVCdmVersion(&cdm_version)) {
|
|
LOGW("GetWVCdmVersion failed");
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
*query_response = cdm_version;
|
|
return NO_ERROR;
|
|
}
|
|
if (query_token == QUERY_KEY_RESOURCE_RATING_TIER) {
|
|
uint32_t tier;
|
|
if (!crypto_session->GetResourceRatingTier(security_level, &tier)) {
|
|
LOGW("GetResourceRatingTier failed");
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
*query_response = std::to_string(tier);
|
|
return NO_ERROR;
|
|
}
|
|
if (query_token == QUERY_KEY_OEMCRYPTO_BUILD_INFORMATION) {
|
|
if (!crypto_session->GetBuildInformation(security_level, query_response)) {
|
|
LOGW("GetBuildInformation failed");
|
|
query_response->clear();
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
if (query_token == QUERY_KEY_DECRYPT_HASH_SUPPORT) {
|
|
uint32_t hash_support = 0;
|
|
if (!crypto_session->GetDecryptHashSupport(security_level, &hash_support)) {
|
|
LOGW("GetDecryptHashSupport failed");
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
*query_response = std::to_string(hash_support);
|
|
return NO_ERROR;
|
|
}
|
|
if (query_token == QUERY_KEY_PROVISIONING_MODEL) {
|
|
CdmClientTokenType token_type = kClientTokenUninitialized;
|
|
const CdmResponseType status =
|
|
crypto_session->GetProvisioningMethod(security_level, &token_type);
|
|
if (status != NO_ERROR) {
|
|
LOGW("GetProvisioningMethod failed: status = %d",
|
|
static_cast<int>(status));
|
|
return status;
|
|
}
|
|
switch (token_type) {
|
|
case kClientTokenDrmCert:
|
|
*query_response = QUERY_VALUE_DRM_CERTIFICATE;
|
|
break;
|
|
case kClientTokenKeybox:
|
|
*query_response = QUERY_VALUE_KEYBOX;
|
|
break;
|
|
case kClientTokenOemCert:
|
|
*query_response = QUERY_VALUE_OEM_CERTIFICATE;
|
|
break;
|
|
case kClientTokenUninitialized:
|
|
default:
|
|
LOGW("GetProvisioningMethod returned invalid method: token_type = %d",
|
|
static_cast<int>(token_type));
|
|
return GET_PROVISIONING_METHOD_ERROR;
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
if (query_token == QUERY_KEY_MAX_USAGE_TABLE_ENTRIES) {
|
|
size_t max_number_of_usage_entries;
|
|
if (!crypto_session->GetMaximumUsageTableEntries(
|
|
security_level, &max_number_of_usage_entries)) {
|
|
LOGW("GetMaxUsageTableEntries failed");
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
if (max_number_of_usage_entries == 0) {
|
|
// Zero indicates that the table is dynamically allocated and does
|
|
// not have a defined limit. Setting to max value of int32_t to
|
|
// be able to fit into a Java int.
|
|
max_number_of_usage_entries = std::numeric_limits<int32_t>::max();
|
|
}
|
|
*query_response = std::to_string(max_number_of_usage_entries);
|
|
return NO_ERROR;
|
|
}
|
|
if (query_token == QUERY_KEY_OEMCRYPTO_API_MINOR_VERSION) {
|
|
uint32_t api_minor_version;
|
|
if (!crypto_session->GetApiMinorVersion(security_level,
|
|
&api_minor_version)) {
|
|
LOGW("GetApiMinorVersion failed");
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
*query_response = std::to_string(api_minor_version);
|
|
return NO_ERROR;
|
|
}
|
|
if (query_token == QUERY_KEY_ANALOG_OUTPUT_CAPABILITIES) {
|
|
bool supported = false, can_disable = false, cgms_a = false;
|
|
if (crypto_session->GetAnalogOutputCapabilities(&supported, &can_disable,
|
|
&cgms_a)) {
|
|
if (supported) {
|
|
if (cgms_a) {
|
|
*query_response = QUERY_VALUE_CGMS_A;
|
|
} else {
|
|
*query_response = QUERY_VALUE_SUPPORTED;
|
|
}
|
|
} else {
|
|
*query_response = QUERY_VALUE_NONE;
|
|
}
|
|
} else {
|
|
*query_response = QUERY_VALUE_UNKNOWN;
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
if (query_token == QUERY_KEY_CAN_DISABLE_ANALOG_OUTPUT) {
|
|
bool supported = false, can_disable = false, cgms_a = false;
|
|
if (crypto_session->GetAnalogOutputCapabilities(&supported, &can_disable,
|
|
&cgms_a)) {
|
|
*query_response = can_disable ? QUERY_VALUE_TRUE : QUERY_VALUE_FALSE;
|
|
} else {
|
|
*query_response = QUERY_VALUE_UNKNOWN;
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
|
|
CdmResponseType status;
|
|
M_TIME(status = crypto_session->Open(security_level),
|
|
metrics_->GetCryptoMetrics(), crypto_session_open_, status,
|
|
security_level);
|
|
|
|
if (status != NO_ERROR) return status;
|
|
|
|
// Add queries here, that need an open session before they can be answered
|
|
if (query_token == QUERY_KEY_DEVICE_ID) {
|
|
std::string device_id;
|
|
status = crypto_session->GetExternalDeviceUniqueId(&device_id);
|
|
metrics_->GetCryptoMetrics()
|
|
->crypto_session_get_device_unique_id_.Increment(status);
|
|
if (status != NO_ERROR) return status;
|
|
*query_response = device_id;
|
|
return NO_ERROR;
|
|
}
|
|
if (query_token == QUERY_KEY_SYSTEM_ID) {
|
|
uint32_t system_id;
|
|
const bool got_id = crypto_session->GetSystemId(&system_id);
|
|
if (!got_id) {
|
|
LOGW("QUERY_KEY_SYSTEM_ID unknown failure");
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
*query_response = std::to_string(system_id);
|
|
return NO_ERROR;
|
|
}
|
|
if (query_token == QUERY_KEY_PROVISIONING_ID) {
|
|
std::string provisioning_id;
|
|
status = crypto_session->GetProvisioningId(&provisioning_id);
|
|
if (status != NO_ERROR) {
|
|
LOGW("GetProvisioningId failed: status = %d", static_cast<int>(status));
|
|
return status;
|
|
}
|
|
*query_response = provisioning_id;
|
|
return NO_ERROR;
|
|
}
|
|
LOGW("Unknown status requested: query_token = %s", IdToString(query_token));
|
|
return INVALID_QUERY_KEY;
|
|
}
|
|
|
|
CdmResponseType CdmEngine::QuerySessionStatus(const CdmSessionId& session_id,
|
|
CdmQueryMap* query_response) {
|
|
LOGI("session_id = %s", IdToString(session_id));
|
|
std::shared_ptr<CdmSession> session;
|
|
if (!session_map_.FindSession(session_id, &session)) {
|
|
LOGE("Session not found: session_id = %s", IdToString(session_id));
|
|
return SESSION_NOT_FOUND_8;
|
|
}
|
|
return session->QueryStatus(query_response);
|
|
}
|
|
|
|
bool CdmEngine::IsReleaseSession(const CdmSessionId& session_id) {
|
|
LOGI("session_id = %s", IdToString(session_id));
|
|
std::shared_ptr<CdmSession> session;
|
|
if (!session_map_.FindSession(session_id, &session)) {
|
|
LOGE("Session not found: session_id = %s", IdToString(session_id));
|
|
return false;
|
|
}
|
|
return session->is_release();
|
|
}
|
|
|
|
bool CdmEngine::IsOfflineSession(const CdmSessionId& session_id) {
|
|
LOGI("session_id = %s", IdToString(session_id));
|
|
std::shared_ptr<CdmSession> session;
|
|
if (!session_map_.FindSession(session_id, &session)) {
|
|
LOGE("Session not found: session_id = %s", IdToString(session_id));
|
|
return false;
|
|
}
|
|
return session->is_offline();
|
|
}
|
|
|
|
CdmResponseType CdmEngine::QueryKeyStatus(const CdmSessionId& session_id,
|
|
CdmQueryMap* query_response) {
|
|
LOGI("session_id = %s", IdToString(session_id));
|
|
std::shared_ptr<CdmSession> session;
|
|
if (!session_map_.FindSession(session_id, &session)) {
|
|
LOGE("Session not found: session_id = %s", IdToString(session_id));
|
|
return SESSION_NOT_FOUND_9;
|
|
}
|
|
return session->QueryKeyStatus(query_response);
|
|
}
|
|
|
|
CdmResponseType CdmEngine::QueryKeyAllowedUsage(const CdmSessionId& session_id,
|
|
const std::string& key_id,
|
|
CdmKeyAllowedUsage* key_usage) {
|
|
LOGI("session_id = %s, key_id = %s", IdToString(session_id),
|
|
IdToString(key_id));
|
|
if (key_usage == nullptr) {
|
|
LOGE("Output |key_usage| is null");
|
|
return PARAMETER_NULL;
|
|
}
|
|
std::shared_ptr<CdmSession> session;
|
|
if (!session_map_.FindSession(session_id, &session)) {
|
|
LOGE("Session not found: session_id = %s", IdToString(session_id));
|
|
return SESSION_NOT_FOUND_12;
|
|
}
|
|
return session->QueryKeyAllowedUsage(key_id, key_usage);
|
|
}
|
|
|
|
CdmResponseType CdmEngine::QueryKeyAllowedUsage(const std::string& key_id,
|
|
CdmKeyAllowedUsage* key_usage) {
|
|
LOGI("key_id = %s", IdToString(key_id));
|
|
if (!key_usage) {
|
|
LOGE("Output |key_usage| is null");
|
|
return PARAMETER_NULL;
|
|
}
|
|
key_usage->Clear();
|
|
CdmSessionList sessions;
|
|
session_map_.GetSessionList(sessions);
|
|
bool found = false;
|
|
for (CdmSessionList::iterator iter = sessions.begin(); iter != sessions.end();
|
|
++iter) {
|
|
CdmKeyAllowedUsage found_in_this_session;
|
|
const CdmResponseType sts =
|
|
(*iter)->QueryKeyAllowedUsage(key_id, &found_in_this_session);
|
|
if (sts == NO_ERROR) {
|
|
if (found) {
|
|
// Found another key. If usage settings do not match, fail.
|
|
if (!key_usage->Equals(found_in_this_session)) {
|
|
key_usage->Clear();
|
|
return KEY_CONFLICT_1;
|
|
}
|
|
} else {
|
|
*key_usage = found_in_this_session;
|
|
found = true;
|
|
}
|
|
} else if (sts != KEY_NOT_FOUND_1) {
|
|
LOGE("QueryKeyAllowedUsage failed: status = %d", static_cast<int>(sts));
|
|
key_usage->Clear();
|
|
return sts;
|
|
}
|
|
}
|
|
return (found) ? NO_ERROR : KEY_NOT_FOUND_2;
|
|
}
|
|
|
|
CdmResponseType CdmEngine::QueryOemCryptoSessionId(
|
|
const CdmSessionId& session_id, CdmQueryMap* query_response) {
|
|
LOGI("session_id = %s", IdToString(session_id));
|
|
std::shared_ptr<CdmSession> session;
|
|
if (!session_map_.FindSession(session_id, &session)) {
|
|
LOGE("Session not found: session_id = %s", IdToString(session_id));
|
|
return SESSION_NOT_FOUND_10;
|
|
}
|
|
return session->QueryOemCryptoSessionId(query_response);
|
|
}
|
|
|
|
// 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,
|
|
SecurityLevel requested_security_level, CdmProvisioningRequest* request,
|
|
std::string* default_url) {
|
|
LOGI("cert_type = %s", CdmCertificateTypeToString(cert_type));
|
|
if (!request) {
|
|
LOGE("Output |request| is null");
|
|
return INVALID_PROVISIONING_REQUEST_PARAM_1;
|
|
}
|
|
if (!default_url) {
|
|
LOGE("Output |default_url| is null");
|
|
return INVALID_PROVISIONING_REQUEST_PARAM_2;
|
|
}
|
|
|
|
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 NO_ERROR;
|
|
if (status == NOT_IMPLEMENTED_ERROR) {
|
|
LOGW("OKP not supoprted, falling back to L3");
|
|
OkpTriggerFallback();
|
|
requested_security_level = kLevel3;
|
|
} else if (status == OKP_ALREADY_PROVISIONED) {
|
|
LOGD("OKP already completed, continuing in normal operation");
|
|
OkpCleanUp();
|
|
// Continue with normal provisioning request.
|
|
} else {
|
|
LOGE("Failed to generate OKP request: status = %d",
|
|
static_cast<int>(status));
|
|
return status;
|
|
}
|
|
}
|
|
} else {
|
|
std::unique_lock<std::mutex> lock(okp_mutex_);
|
|
if (okp_fallback_) {
|
|
requested_security_level = kLevel3;
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO(b/141705730): Remove usage entries on provisioning.
|
|
if (!cert_provisioning_) {
|
|
cert_provisioning_.reset(
|
|
new CertificateProvisioning(metrics_->GetCryptoMetrics()));
|
|
const CdmResponseType status =
|
|
cert_provisioning_->Init(service_certificate);
|
|
if (status != NO_ERROR) return status;
|
|
}
|
|
const CdmResponseType status = cert_provisioning_->GetProvisioningRequest(
|
|
requested_security_level, cert_type, cert_authority,
|
|
file_system_->origin(), spoid_, request, default_url);
|
|
if (status != NO_ERROR) {
|
|
cert_provisioning_.reset(); // Release resources.
|
|
}
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* The response message consists of a device certificate and the device RSA key.
|
|
* The device RSA key is stored in the T.E.E. The device certificate is stored
|
|
* in the device.
|
|
*
|
|
* Returns NO_ERROR for success and CdmResponseType error code if fails.
|
|
*/
|
|
CdmResponseType CdmEngine::HandleProvisioningResponse(
|
|
const CdmProvisioningResponse& response,
|
|
SecurityLevel requested_security_level, std::string* cert,
|
|
std::string* wrapped_key) {
|
|
LOGI("response_size = %zu, security_level = %s", response.size(),
|
|
SecurityLevelToString(requested_security_level));
|
|
if (response.empty()) {
|
|
LOGE("Empty provisioning response");
|
|
cert_provisioning_.reset();
|
|
return EMPTY_PROVISIONING_RESPONSE;
|
|
}
|
|
if (cert == nullptr) {
|
|
LOGE("Output |cert| is null");
|
|
cert_provisioning_.reset();
|
|
return INVALID_PROVISIONING_PARAMETERS_1;
|
|
}
|
|
if (wrapped_key == nullptr) {
|
|
LOGE("Output |wrapped_key| is null");
|
|
cert_provisioning_.reset();
|
|
return INVALID_PROVISIONING_PARAMETERS_2;
|
|
}
|
|
|
|
if (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 EMPTY_PROVISIONING_CERTIFICATE_2;
|
|
}
|
|
CdmSecurityLevel security_level = crypto_session->GetSecurityLevel();
|
|
if (!IsProvisioned(security_level)) {
|
|
LOGE("Provisioning object missing");
|
|
return EMPTY_PROVISIONING_CERTIFICATE_1;
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
|
|
const CdmResponseType ret = cert_provisioning_->HandleProvisioningResponse(
|
|
file_system_, response, cert, wrapped_key);
|
|
// Release resources only on success. It is possible that a provisioning
|
|
// attempt was made after this one was requested but before the response was
|
|
// received, which will cause this attempt to fail. Not releasing will
|
|
// allow for the possibility that the later attempt succeeds.
|
|
if (NO_ERROR == ret) cert_provisioning_.reset();
|
|
return ret;
|
|
}
|
|
|
|
bool CdmEngine::IsProvisioned(CdmSecurityLevel security_level) {
|
|
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);
|
|
|
|
CdmSession session(file_system_, metrics_->AddSession());
|
|
const CdmResponseType status = session.Init(&property_set);
|
|
if (NO_ERROR != status) {
|
|
LOGE("Init failed: status = %d", static_cast<int>(status));
|
|
}
|
|
return status == NO_ERROR;
|
|
}
|
|
|
|
CdmResponseType CdmEngine::Unprovision(CdmSecurityLevel security_level) {
|
|
LOGI("security_level = %s", CdmSecurityLevelToString(security_level));
|
|
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 DEVICE_CANNOT_REPROVISION;
|
|
}
|
|
|
|
DeviceFiles handle(file_system_);
|
|
if (!handle.Init(security_level)) {
|
|
LOGE("Unable to initialize device files");
|
|
return UNPROVISION_ERROR_1;
|
|
}
|
|
|
|
// TODO(b/141705730): Remove usage entries during unprovisioning.
|
|
if (!file_system_->IsGlobal()) {
|
|
if (!handle.RemoveCertificate()) {
|
|
LOGE("Unable to delete certificate");
|
|
return UNPROVISION_ERROR_2;
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
if (!handle.DeleteAllFiles()) {
|
|
LOGE("Unable to delete files");
|
|
return UNPROVISION_ERROR_3;
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
|
|
CdmResponseType CdmEngine::ListStoredLicenses(
|
|
CdmSecurityLevel security_level, std::vector<std::string>* key_set_ids) {
|
|
if (!key_set_ids) {
|
|
LOGE("Output |key_set_ids| is null");
|
|
return 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 LIST_LICENSE_ERROR_1;
|
|
}
|
|
if (!handle.ListLicenses(key_set_ids)) {
|
|
LOGE("ListLicenses call failed");
|
|
return LIST_LICENSE_ERROR_2;
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
|
|
CdmResponseType CdmEngine::ListUsageIds(
|
|
const std::string& app_id, CdmSecurityLevel security_level,
|
|
std::vector<std::string>* ksids,
|
|
std::vector<std::string>* provider_session_tokens) {
|
|
if (!ksids && !provider_session_tokens) {
|
|
LOGE("Outputs |ksids| and |provider_session_tokens| are null");
|
|
return 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 LIST_USAGE_ERROR_1;
|
|
}
|
|
if (!handle.ListUsageIds(app_id, ksids, provider_session_tokens)) {
|
|
LOGE("Failed: app_id = %s, security_level = %s", IdToString(app_id),
|
|
CdmSecurityLevelToString(security_level));
|
|
return LIST_USAGE_ERROR_2;
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
|
|
CdmResponseType CdmEngine::DeleteUsageRecord(const std::string& app_id,
|
|
CdmSecurityLevel security_level,
|
|
const std::string& key_set_id) {
|
|
LOGI("app_id = %s, key_set_id = %s", IdToString(app_id),
|
|
IdToString(key_set_id));
|
|
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 DELETE_USAGE_ERROR_1;
|
|
}
|
|
std::string provider_session_token;
|
|
if (!handle.GetProviderSessionToken(app_id, key_set_id,
|
|
&provider_session_token)) {
|
|
LOGE("GetProviderSessionToken failed");
|
|
return DELETE_USAGE_ERROR_2;
|
|
}
|
|
return RemoveUsageInfo(app_id, provider_session_token);
|
|
}
|
|
|
|
CdmResponseType CdmEngine::GetOfflineLicenseState(
|
|
const CdmKeySetId& key_set_id, CdmSecurityLevel security_level,
|
|
CdmOfflineLicenseState* license_state) {
|
|
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 GET_OFFLINE_LICENSE_STATE_ERROR_1;
|
|
}
|
|
DeviceFiles::CdmLicenseData license_data;
|
|
DeviceFiles::ResponseType sub_error_code = DeviceFiles::kNoError;
|
|
if (!handle.RetrieveLicense(key_set_id, &license_data, &sub_error_code)) {
|
|
LOGE("Failed to retrieve license state: key_set_id = %s",
|
|
IdToString(key_set_id));
|
|
return GET_OFFLINE_LICENSE_STATE_ERROR_2;
|
|
}
|
|
*license_state = license_data.state;
|
|
return 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 REMOVE_OFFLINE_LICENSE_ERROR_1;
|
|
}
|
|
|
|
CdmResponseType sts = OpenKeySetSession(key_set_id, &property_set,
|
|
nullptr /* event listener */);
|
|
if (sts != NO_ERROR) {
|
|
LOGE("OpenKeySetSession failed: status = %d", static_cast<int>(sts));
|
|
handle.DeleteLicense(key_set_id);
|
|
return sts;
|
|
}
|
|
|
|
CdmSessionId session_id;
|
|
CdmAppParameterMap dummy_app_params;
|
|
const InitializationData dummy_init_data("", "", "");
|
|
CdmKeyRequest key_request;
|
|
// Calling with no session_id is okay
|
|
sts = GenerateKeyRequest(session_id, key_set_id, dummy_init_data,
|
|
kLicenseTypeRelease, dummy_app_params, &key_request);
|
|
if (sts == KEY_MESSAGE) {
|
|
std::unique_lock<std::mutex> lock(release_key_sets_lock_);
|
|
CdmReleaseKeySetMap::iterator iter = release_key_sets_.find(key_set_id);
|
|
if (iter == release_key_sets_.end()) {
|
|
LOGE("Key set not found: key_set_id = %s", IdToString(key_set_id));
|
|
sts = REMOVE_OFFLINE_LICENSE_ERROR_2;
|
|
} else {
|
|
session_id = iter->second.first;
|
|
sts = RemoveLicense(session_id);
|
|
}
|
|
} else if (sts == LICENSE_USAGE_ENTRY_MISSING) {
|
|
// It is possible that the CDM is tracking a key set ID, but has
|
|
// removed the usage information associated with it. In this case,
|
|
// it will no longer be possible to load the license for release;
|
|
// and the file should simply be deleted.
|
|
LOGW("License usage entry is missing, deleting license file");
|
|
handle.DeleteLicense(key_set_id);
|
|
sts = NO_ERROR;
|
|
}
|
|
|
|
if (sts != NO_ERROR) {
|
|
LOGE("GenerateKeyRequest failed: status = %d", static_cast<int>(sts));
|
|
handle.DeleteLicense(key_set_id);
|
|
}
|
|
CloseKeySetSession(key_set_id);
|
|
return sts;
|
|
}
|
|
|
|
CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id,
|
|
const CdmSecureStopId& ssid,
|
|
int* error_detail,
|
|
CdmUsageInfo* usage_info) {
|
|
LOGI("app_id = %s, ssid = %s", IdToString(app_id), IdToString(ssid));
|
|
if (!usage_property_set_) {
|
|
usage_property_set_.reset(new UsagePropertySet());
|
|
}
|
|
if (usage_info == nullptr) {
|
|
LOGE("Output |usage_info| is null");
|
|
return PARAMETER_NULL;
|
|
}
|
|
usage_property_set_->set_security_level(kLevelDefault);
|
|
usage_property_set_->set_app_id(app_id);
|
|
usage_session_.reset(new CdmSession(file_system_, metrics_->AddSession()));
|
|
CdmResponseType status = usage_session_->Init(usage_property_set_.get());
|
|
if (NO_ERROR != status) {
|
|
LOGE("Session init error: status = %d", static_cast<int>(status));
|
|
return status;
|
|
}
|
|
DeviceFiles handle(file_system_);
|
|
if (!handle.Init(usage_session_->GetSecurityLevel())) {
|
|
LOGE("Device file init error");
|
|
return GET_USAGE_INFO_ERROR_1;
|
|
}
|
|
|
|
DeviceFiles::CdmUsageData usage_data;
|
|
if (!handle.RetrieveUsageInfo(DeviceFiles::GetUsageInfoFileName(app_id), ssid,
|
|
&usage_data)) {
|
|
usage_property_set_->set_security_level(kLevel3);
|
|
usage_property_set_->set_app_id(app_id);
|
|
usage_session_.reset(new CdmSession(file_system_, metrics_->AddSession()));
|
|
status = usage_session_->Init(usage_property_set_.get());
|
|
if (NO_ERROR != status) {
|
|
LOGE("Session init error: status = %d", static_cast<int>(status));
|
|
return status;
|
|
}
|
|
if (!handle.Reset(usage_session_->GetSecurityLevel())) {
|
|
LOGE("Device file init error");
|
|
return GET_USAGE_INFO_ERROR_2;
|
|
}
|
|
if (!handle.RetrieveUsageInfo(DeviceFiles::GetUsageInfoFileName(app_id),
|
|
ssid, &usage_data)) {
|
|
// No entry found for that ssid.
|
|
return USAGE_INFO_NOT_FOUND;
|
|
}
|
|
}
|
|
|
|
status = usage_session_->RestoreUsageSession(usage_data, error_detail);
|
|
|
|
if (KEY_ADDED != status) {
|
|
LOGE("RestoreUsageSession failed: status = %d", static_cast<int>(status));
|
|
usage_info->clear();
|
|
return status;
|
|
}
|
|
|
|
CdmKeyRequest request;
|
|
status = usage_session_->GenerateReleaseRequest(&request);
|
|
|
|
usage_info->clear();
|
|
usage_info->push_back(request.message);
|
|
|
|
if (KEY_MESSAGE != status) {
|
|
LOGE("GenerateReleaseRequest failed: status = %d",
|
|
static_cast<int>(status));
|
|
usage_info->clear();
|
|
return status;
|
|
}
|
|
|
|
return KEY_MESSAGE;
|
|
}
|
|
|
|
CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id,
|
|
int* error_detail,
|
|
CdmUsageInfo* usage_info) {
|
|
LOGI("app_id = %s", IdToString(app_id));
|
|
if (usage_info == nullptr) {
|
|
LOGE("Output |usage_info| is null");
|
|
return PARAMETER_NULL;
|
|
}
|
|
// Return a random usage report from a random security level
|
|
SecurityLevel security_level =
|
|
CdmRandom::RandomBool() ? kLevelDefault : kLevel3;
|
|
CdmResponseType status = UNKNOWN_ERROR;
|
|
do {
|
|
status = GetUsageInfo(app_id, security_level, error_detail, usage_info);
|
|
if (KEY_MESSAGE == status && !usage_info->empty()) {
|
|
return status;
|
|
}
|
|
} while (KEY_CANCELED == status);
|
|
|
|
security_level = (kLevel3 == security_level) ? kLevelDefault : kLevel3;
|
|
do {
|
|
status = GetUsageInfo(app_id, security_level, error_detail, usage_info);
|
|
if (NEED_PROVISIONING == status)
|
|
return NO_ERROR; // Valid scenario that one of the security
|
|
// levels has not been provisioned
|
|
} while (KEY_CANCELED == status);
|
|
return status;
|
|
}
|
|
|
|
CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id,
|
|
SecurityLevel requested_security_level,
|
|
int* error_detail,
|
|
CdmUsageInfo* usage_info) {
|
|
LOGI("app_id = %s, security_level = %s", IdToString(app_id),
|
|
SecurityLevelToString(requested_security_level));
|
|
if (usage_info == nullptr) {
|
|
LOGE("Output |usage_info| is null");
|
|
return PARAMETER_NULL;
|
|
}
|
|
if (requested_security_level == kLevelDefault && OkpIsInFallbackMode()) {
|
|
LOGD("OKP fallback to L3");
|
|
requested_security_level = kLevel3;
|
|
}
|
|
if (!usage_property_set_) {
|
|
usage_property_set_.reset(new UsagePropertySet());
|
|
}
|
|
usage_property_set_->set_security_level(requested_security_level);
|
|
usage_property_set_->set_app_id(app_id);
|
|
|
|
usage_session_.reset(new CdmSession(file_system_, metrics_->AddSession()));
|
|
|
|
CdmResponseType status = usage_session_->Init(usage_property_set_.get());
|
|
if (NO_ERROR != status) {
|
|
LOGE("Session init error");
|
|
return status;
|
|
}
|
|
|
|
DeviceFiles handle(file_system_);
|
|
if (!handle.Init(usage_session_->GetSecurityLevel())) {
|
|
LOGE("Unable to initialize device files");
|
|
return GET_USAGE_INFO_ERROR_3;
|
|
}
|
|
|
|
std::vector<DeviceFiles::CdmUsageData> usage_data;
|
|
if (!handle.RetrieveUsageInfo(DeviceFiles::GetUsageInfoFileName(app_id),
|
|
&usage_data)) {
|
|
LOGE("Unable to read usage information");
|
|
return GET_USAGE_INFO_ERROR_4;
|
|
}
|
|
|
|
if (usage_data.empty()) {
|
|
usage_info->clear();
|
|
return NO_ERROR;
|
|
}
|
|
|
|
const size_t index = CdmRandom::RandomInRange(usage_data.size() - 1);
|
|
status = usage_session_->RestoreUsageSession(usage_data[index], error_detail);
|
|
if (KEY_ADDED != status) {
|
|
// TODO(b/141704872): Make multiple attempts.
|
|
LOGE("RestoreUsageSession failed: index = %zu, status = %d", index,
|
|
static_cast<int>(status));
|
|
usage_info->clear();
|
|
return status;
|
|
}
|
|
|
|
CdmKeyRequest request;
|
|
status = usage_session_->GenerateReleaseRequest(&request);
|
|
|
|
usage_info->clear();
|
|
usage_info->push_back(request.message);
|
|
|
|
switch (status) {
|
|
case KEY_MESSAGE:
|
|
break;
|
|
case KEY_CANCELED: // usage information not present in
|
|
usage_session_->DeleteLicenseFile(); // OEMCrypto, delete and try again
|
|
usage_info->clear();
|
|
break;
|
|
default:
|
|
LOGE("GenerateReleaseRequest failed: status = %d",
|
|
static_cast<int>(status));
|
|
usage_info->clear();
|
|
break;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
CdmResponseType CdmEngine::RemoveAllUsageInfo(
|
|
const std::string& app_id, CdmSecurityLevel cdm_security_level) {
|
|
LOGI("app_id = %s, security_level = %s", IdToString(app_id),
|
|
CdmSecurityLevelToString(cdm_security_level));
|
|
if (!usage_property_set_) {
|
|
usage_property_set_.reset(new UsagePropertySet());
|
|
}
|
|
usage_property_set_->set_app_id(app_id);
|
|
|
|
CdmResponseType status = NO_ERROR;
|
|
DeviceFiles handle(file_system_);
|
|
if (handle.Init(cdm_security_level)) {
|
|
const SecurityLevel security_level =
|
|
cdm_security_level == kSecurityLevelL3 ? kLevel3 : kLevelDefault;
|
|
usage_property_set_->set_security_level(security_level);
|
|
usage_session_.reset(new CdmSession(file_system_, metrics_->AddSession()));
|
|
usage_session_->Init(usage_property_set_.get());
|
|
|
|
if (usage_session_->supports_usage_info()) {
|
|
std::vector<DeviceFiles::CdmUsageData> usage_data;
|
|
// Retrieve all usage information but delete only one before
|
|
// refetching. This is because deleting the usage entry
|
|
// might cause other entries to be shifted and information updated.
|
|
do {
|
|
if (!handle.RetrieveUsageInfo(DeviceFiles::GetUsageInfoFileName(app_id),
|
|
&usage_data)) {
|
|
LOGW("Failed to retrieve usage info");
|
|
break;
|
|
}
|
|
|
|
if (usage_data.empty()) break;
|
|
|
|
CdmResponseType res =
|
|
usage_session_->DeleteUsageEntry(usage_data[0].usage_entry_number);
|
|
|
|
if (res != NO_ERROR) {
|
|
LOGW("Failed to delete usage entry: status = %d",
|
|
static_cast<int>(res));
|
|
break;
|
|
}
|
|
|
|
if (!handle.DeleteUsageInfo(DeviceFiles::GetUsageInfoFileName(app_id),
|
|
usage_data[0].provider_session_token)) {
|
|
LOGW("Failed to delete usage info");
|
|
break;
|
|
}
|
|
} while (!usage_data.empty());
|
|
|
|
std::vector<std::string> provider_session_tokens;
|
|
if (!handle.DeleteAllUsageInfoForApp(
|
|
DeviceFiles::GetUsageInfoFileName(app_id),
|
|
&provider_session_tokens)) {
|
|
status = REMOVE_ALL_USAGE_INFO_ERROR_5;
|
|
}
|
|
}
|
|
}
|
|
usage_session_.reset();
|
|
return status;
|
|
}
|
|
|
|
CdmResponseType CdmEngine::RemoveAllUsageInfo(const std::string& app_id) {
|
|
LOGI("app_id = %s", IdToString(app_id));
|
|
const CdmResponseType status_l1 =
|
|
RemoveAllUsageInfo(app_id, kSecurityLevelL1);
|
|
const CdmResponseType status_l3 =
|
|
RemoveAllUsageInfo(app_id, kSecurityLevelL3);
|
|
// Prioritizing L1 status.
|
|
if (status_l1 != NO_ERROR) {
|
|
return status_l1;
|
|
}
|
|
return status_l3;
|
|
}
|
|
|
|
CdmResponseType CdmEngine::RemoveUsageInfo(
|
|
const std::string& app_id, const CdmSecureStopId& provider_session_token) {
|
|
LOGI("app_id = %s, pst = %s", IdToString(app_id),
|
|
IdToString(provider_session_token));
|
|
if (!usage_property_set_) {
|
|
usage_property_set_.reset(new UsagePropertySet());
|
|
}
|
|
usage_property_set_->set_app_id(app_id);
|
|
|
|
CdmResponseType status = NO_ERROR;
|
|
for (int j = kSecurityLevelL1; j < kSecurityLevelUnknown; ++j) {
|
|
DeviceFiles handle(file_system_);
|
|
if (handle.Init(static_cast<CdmSecurityLevel>(j))) {
|
|
SecurityLevel security_level =
|
|
static_cast<CdmSecurityLevel>(j) == kSecurityLevelL3 ? kLevel3
|
|
: kLevelDefault;
|
|
usage_property_set_->set_security_level(security_level);
|
|
usage_session_.reset(
|
|
new CdmSession(file_system_, metrics_->AddSession()));
|
|
usage_session_->Init(usage_property_set_.get());
|
|
|
|
CdmKeyMessage license_request;
|
|
CdmKeyResponse license_response;
|
|
CdmUsageEntry usage_entry;
|
|
uint32_t usage_entry_number;
|
|
std::string drm_certificate;
|
|
CryptoWrappedKey wrapped_private_key;
|
|
|
|
if (!handle.RetrieveUsageInfo(
|
|
DeviceFiles::GetUsageInfoFileName(app_id), provider_session_token,
|
|
&license_request, &license_response, &usage_entry,
|
|
&usage_entry_number, &drm_certificate, &wrapped_private_key)) {
|
|
// Try other security level
|
|
continue;
|
|
}
|
|
|
|
if (usage_session_->supports_usage_info()) {
|
|
status = usage_session_->DeleteUsageEntry(usage_entry_number);
|
|
if (!handle.DeleteUsageInfo(DeviceFiles::GetUsageInfoFileName(app_id),
|
|
provider_session_token)) {
|
|
status = REMOVE_USAGE_INFO_ERROR_1;
|
|
}
|
|
usage_session_.reset();
|
|
return status;
|
|
}
|
|
} else {
|
|
LOGE("Failed to initialize L%d device files", j);
|
|
status = REMOVE_USAGE_INFO_ERROR_2;
|
|
}
|
|
}
|
|
usage_session_.reset();
|
|
return REMOVE_USAGE_INFO_ERROR_3;
|
|
}
|
|
|
|
CdmResponseType CdmEngine::ReleaseUsageInfo(
|
|
const CdmUsageInfoReleaseMessage& message) {
|
|
LOGI("message_size = %zu", message.size());
|
|
if (!usage_session_) {
|
|
LOGE("Usage session not initialized");
|
|
return RELEASE_USAGE_INFO_ERROR;
|
|
}
|
|
const CdmResponseType status = usage_session_->ReleaseKey(message);
|
|
usage_session_.reset();
|
|
if (NO_ERROR != status) {
|
|
LOGE("ReleaseKey failed: status = %d", status);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
CdmResponseType CdmEngine::LoadUsageSession(const CdmKeySetId& key_set_id,
|
|
CdmKeyMessage* release_message) {
|
|
LOGI("key_set_id = %s", IdToString(key_set_id));
|
|
// This method is currently only used by the CE CDM, in which all session IDs
|
|
// are key set IDs.
|
|
assert(Properties::AlwaysUseKeySetIds());
|
|
if (key_set_id.empty()) {
|
|
LOGE("Invalid key set ID");
|
|
return EMPTY_KEYSET_ID_ENG_5;
|
|
}
|
|
if (release_message == nullptr) {
|
|
LOGE("Output |release_message| is null");
|
|
return PARAMETER_NULL;
|
|
}
|
|
|
|
std::shared_ptr<CdmSession> session;
|
|
if (!session_map_.FindSession(key_set_id, &session)) {
|
|
LOGE("Session not found: key_set_id = %s", IdToString(key_set_id));
|
|
return SESSION_NOT_FOUND_11;
|
|
}
|
|
|
|
DeviceFiles handle(file_system_);
|
|
if (!handle.Init(session->GetSecurityLevel())) {
|
|
LOGE("Unable to initialize device files");
|
|
return LOAD_USAGE_INFO_FILE_ERROR;
|
|
}
|
|
|
|
std::string app_id;
|
|
session->GetApplicationId(&app_id);
|
|
|
|
DeviceFiles::CdmUsageData usage_data;
|
|
if (!handle.RetrieveUsageInfoByKeySetId(
|
|
DeviceFiles::GetUsageInfoFileName(app_id), key_set_id,
|
|
&(usage_data.provider_session_token), &(usage_data.license_request),
|
|
&(usage_data.license), &(usage_data.usage_entry),
|
|
&(usage_data.usage_entry_number), &(usage_data.drm_certificate),
|
|
&(usage_data.wrapped_private_key))) {
|
|
LOGE("Unable to find usage information");
|
|
return LOAD_USAGE_INFO_MISSING;
|
|
}
|
|
|
|
int error_detail = NO_ERROR;
|
|
usage_data.key_set_id = key_set_id;
|
|
CdmResponseType status =
|
|
session->RestoreUsageSession(usage_data, &error_detail);
|
|
session->GetMetrics()->cdm_session_restore_usage_session_.Increment(
|
|
status, error_detail);
|
|
if (KEY_ADDED != status) {
|
|
LOGE("Restore failed: key_set_id = %s, status = %d", IdToString(key_set_id),
|
|
static_cast<int>(status));
|
|
return status;
|
|
}
|
|
|
|
CdmKeyRequest request;
|
|
status = session->GenerateReleaseRequest(&request);
|
|
*release_message = std::move(request.message);
|
|
switch (status) {
|
|
case KEY_MESSAGE:
|
|
break;
|
|
case KEY_CANCELED:
|
|
// usage information not present in OEMCrypto, delete and try again
|
|
session->DeleteLicenseFile();
|
|
break;
|
|
default:
|
|
LOGE("GenerateReleaseRequest failed: status = %d",
|
|
static_cast<int>(status));
|
|
break;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
CdmResponseType CdmEngine::DecryptV16(
|
|
const CdmSessionId& session_id,
|
|
const CdmDecryptionParametersV16& parameters) {
|
|
for (const CdmDecryptionSample& sample : parameters.samples) {
|
|
if (sample.encrypt_buffer == nullptr) {
|
|
LOGE("No src encrypt buffer");
|
|
return INVALID_DECRYPT_PARAMETERS_ENG_2;
|
|
}
|
|
|
|
if (sample.decrypt_buffer == nullptr) {
|
|
if (!parameters.is_secure &&
|
|
!Properties::Properties::oem_crypto_use_fifo()) {
|
|
LOGE("No dest decrypt buffer");
|
|
return INVALID_DECRYPT_PARAMETERS_ENG_4;
|
|
}
|
|
// else we must be level 1 direct and we don't need to return a buffer.
|
|
}
|
|
}
|
|
|
|
std::unique_lock<std::recursive_mutex> lock(session_map_lock_);
|
|
std::shared_ptr<CdmSession> session;
|
|
if (session_id.empty()) {
|
|
CdmSessionList sessions;
|
|
session_map_.GetSessionList(sessions);
|
|
|
|
// Loop through the sessions to find the session containing the key_id
|
|
// with the longest remaining license validity.
|
|
int64_t seconds_remaining = 0;
|
|
for (CdmSessionList::iterator iter = sessions.begin();
|
|
iter != sessions.end(); ++iter) {
|
|
if ((*iter)->IsKeyLoaded(parameters.key_id)) {
|
|
int64_t duration = (*iter)->GetDurationRemaining();
|
|
if (duration > seconds_remaining) {
|
|
session = *iter;
|
|
seconds_remaining = duration;
|
|
}
|
|
}
|
|
}
|
|
if (!session) {
|
|
LOGE("Session not found: session_id = <empty>");
|
|
return SESSION_NOT_FOUND_FOR_DECRYPT;
|
|
}
|
|
} else {
|
|
if (!session_map_.FindSession(session_id, &session)) {
|
|
LOGE("Session not found: session_id = %s", IdToString(session_id));
|
|
return SESSION_NOT_FOUND_FOR_DECRYPT;
|
|
}
|
|
}
|
|
|
|
return session->Decrypt(parameters);
|
|
}
|
|
|
|
CdmResponseType CdmEngine::GenericEncrypt(const std::string& session_id,
|
|
const std::string& in_buffer,
|
|
const std::string& key_id,
|
|
const std::string& iv,
|
|
CdmEncryptionAlgorithm algorithm,
|
|
std::string* out_buffer) {
|
|
if (out_buffer == nullptr) {
|
|
LOGE("Output |out_buffer| is null");
|
|
return PARAMETER_NULL;
|
|
}
|
|
std::shared_ptr<CdmSession> session;
|
|
if (!session_map_.FindSession(session_id, &session)) {
|
|
LOGE("Session not found: session_id = %s", IdToString(session_id));
|
|
return SESSION_NOT_FOUND_13;
|
|
}
|
|
return session->GenericEncrypt(in_buffer, key_id, iv, algorithm, out_buffer);
|
|
}
|
|
|
|
CdmResponseType CdmEngine::GenericDecrypt(const std::string& session_id,
|
|
const std::string& in_buffer,
|
|
const std::string& key_id,
|
|
const std::string& iv,
|
|
CdmEncryptionAlgorithm algorithm,
|
|
std::string* out_buffer) {
|
|
if (out_buffer == nullptr) {
|
|
LOGE("Output |out_buffer| is null");
|
|
return PARAMETER_NULL;
|
|
}
|
|
std::shared_ptr<CdmSession> session;
|
|
if (!session_map_.FindSession(session_id, &session)) {
|
|
LOGE("Session not found: session_id = %s", IdToString(session_id));
|
|
return SESSION_NOT_FOUND_14;
|
|
}
|
|
return session->GenericDecrypt(in_buffer, key_id, iv, algorithm, out_buffer);
|
|
}
|
|
|
|
CdmResponseType CdmEngine::GenericSign(const std::string& session_id,
|
|
const std::string& message,
|
|
const std::string& key_id,
|
|
CdmSigningAlgorithm algorithm,
|
|
std::string* signature) {
|
|
if (signature == nullptr) {
|
|
LOGE("Output |signature| is null");
|
|
return PARAMETER_NULL;
|
|
}
|
|
std::shared_ptr<CdmSession> session;
|
|
if (!session_map_.FindSession(session_id, &session)) {
|
|
LOGE("Session not found: session_id = %s", IdToString(session_id));
|
|
return SESSION_NOT_FOUND_15;
|
|
}
|
|
return session->GenericSign(message, key_id, algorithm, signature);
|
|
}
|
|
|
|
CdmResponseType CdmEngine::GenericVerify(const std::string& session_id,
|
|
const std::string& message,
|
|
const std::string& key_id,
|
|
CdmSigningAlgorithm algorithm,
|
|
const std::string& signature) {
|
|
std::shared_ptr<CdmSession> session;
|
|
if (!session_map_.FindSession(session_id, &session)) {
|
|
LOGE("Session not found: session_id = %s", IdToString(session_id));
|
|
return SESSION_NOT_FOUND_16;
|
|
}
|
|
return session->GenericVerify(message, key_id, algorithm, signature);
|
|
}
|
|
|
|
CdmResponseType CdmEngine::ParseDecryptHashString(
|
|
const std::string& hash_string, CdmSessionId* session_id,
|
|
uint32_t* frame_number, std::string* hash) {
|
|
if (session_id == nullptr) {
|
|
LOGE("Output |session_id| is null");
|
|
return PARAMETER_NULL;
|
|
}
|
|
if (frame_number == nullptr) {
|
|
LOGE("Output |frame_number| is null");
|
|
return PARAMETER_NULL;
|
|
}
|
|
if (hash == nullptr) {
|
|
LOGE("Output |hash| is null");
|
|
return PARAMETER_NULL;
|
|
}
|
|
std::stringstream ss;
|
|
std::string token;
|
|
std::vector<std::string> tokens;
|
|
ss.str(hash_string);
|
|
while (getline(ss, token, ',')) {
|
|
tokens.push_back(token);
|
|
}
|
|
if (tokens.size() != 3) {
|
|
LOGE(
|
|
"Hash string has invalid format: "
|
|
"Unexpected number of tokens: %zu (hash_string = %s)",
|
|
tokens.size(), hash_string.c_str());
|
|
return INVALID_DECRYPT_HASH_FORMAT;
|
|
}
|
|
|
|
for (size_t i = 0; i < tokens.size(); ++i) {
|
|
if (tokens[i].empty()) {
|
|
LOGE(
|
|
"Hash string has invalid format: token %zu of length 0: "
|
|
"hash_string = %s",
|
|
i, hash_string.c_str());
|
|
return INVALID_DECRYPT_HASH_FORMAT;
|
|
}
|
|
}
|
|
|
|
*session_id = tokens[0];
|
|
std::istringstream iss(tokens[1]);
|
|
if (!(iss >> *frame_number)) {
|
|
LOGE("Error while trying to convert frame number to a numeric format: %s",
|
|
hash_string.c_str());
|
|
return INVALID_DECRYPT_HASH_FORMAT;
|
|
}
|
|
|
|
*hash = wvcdm::a2bs_hex(tokens[2]);
|
|
if (hash->empty()) {
|
|
LOGE("Malformed hash: %s", hash_string.c_str());
|
|
return INVALID_DECRYPT_HASH_FORMAT;
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
|
|
CdmResponseType CdmEngine::SetDecryptHash(const CdmSessionId& session_id,
|
|
uint32_t frame_number,
|
|
const std::string& hash) {
|
|
LOGI("session_id = %s", IdToString(session_id));
|
|
std::shared_ptr<CdmSession> session;
|
|
if (!session_map_.FindSession(session_id, &session)) {
|
|
return SESSION_NOT_FOUND_20;
|
|
}
|
|
return session->SetDecryptHash(frame_number, hash);
|
|
}
|
|
|
|
CdmResponseType CdmEngine::GetDecryptHashError(const CdmSessionId& session_id,
|
|
std::string* error_string) {
|
|
LOGI("session_id = %s", IdToString(session_id));
|
|
std::shared_ptr<CdmSession> session;
|
|
if (!session_map_.FindSession(session_id, &session)) {
|
|
return SESSION_NOT_FOUND_20;
|
|
}
|
|
return session->GetDecryptHashError(error_string);
|
|
}
|
|
|
|
// TODO(gmorgan) Used? Delete if unused.
|
|
bool CdmEngine::IsKeyLoaded(const KeyId& key_id) {
|
|
CdmSessionList sessions;
|
|
session_map_.GetSessionList(sessions);
|
|
for (CdmSessionList::iterator iter = sessions.begin(); iter != sessions.end();
|
|
++iter) {
|
|
if ((*iter)->IsKeyLoaded(key_id)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CdmEngine::FindSessionForKey(const KeyId& key_id,
|
|
CdmSessionId* session_id) {
|
|
if (session_id == nullptr) {
|
|
LOGE("Output |session_id| is null");
|
|
return false;
|
|
}
|
|
const uint32_t session_sharing_id =
|
|
Properties::GetSessionSharingId(*session_id);
|
|
|
|
std::unique_lock<std::recursive_mutex> lock(session_map_lock_);
|
|
CdmSessionList sessions;
|
|
session_map_.GetSessionList(sessions);
|
|
|
|
CdmSessionList::iterator session_iter = sessions.end();
|
|
int64_t seconds_remaining = 0;
|
|
for (CdmSessionList::iterator iter = sessions.begin(); iter != sessions.end();
|
|
++iter) {
|
|
CdmSessionId id = (*iter)->session_id();
|
|
if (Properties::GetSessionSharingId(id) == session_sharing_id) {
|
|
if ((*iter)->IsKeyLoaded(key_id)) {
|
|
int64_t duration = (*iter)->GetDurationRemaining();
|
|
if (duration > seconds_remaining) {
|
|
session_iter = iter;
|
|
seconds_remaining = duration;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (session_iter != sessions.end()) {
|
|
*session_id = (*session_iter)->session_id();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CdmEngine::NotifyResolution(const CdmSessionId& session_id, uint32_t width,
|
|
uint32_t height) {
|
|
std::shared_ptr<CdmSession> session;
|
|
if (session_map_.FindSession(session_id, &session)) {
|
|
session->NotifyResolution(width, height);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CdmEngine::ValidateKeySystem(const CdmKeySystem& key_system) {
|
|
return (key_system.find("widevine") != std::string::npos);
|
|
}
|
|
|
|
void CdmEngine::OnTimerEvent() {
|
|
Clock clock;
|
|
const uint64_t current_time = clock.GetCurrentTime();
|
|
bool usage_update_period_expired = false;
|
|
if (current_time - last_usage_information_update_time_ >
|
|
kUpdateUsageInformationPeriod) {
|
|
usage_update_period_expired = true;
|
|
last_usage_information_update_time_ = current_time;
|
|
}
|
|
|
|
bool is_initial_usage_update = false;
|
|
bool is_usage_update_needed = false;
|
|
{
|
|
std::unique_lock<std::recursive_mutex> lock(session_map_lock_);
|
|
CdmSessionList sessions;
|
|
session_map_.GetSessionList(sessions);
|
|
|
|
while (!sessions.empty()) {
|
|
is_initial_usage_update = is_initial_usage_update ||
|
|
sessions.front()->is_initial_usage_update();
|
|
is_usage_update_needed =
|
|
is_usage_update_needed || sessions.front()->is_usage_update_needed();
|
|
|
|
sessions.front()->OnTimerEvent(usage_update_period_expired);
|
|
sessions.pop_front();
|
|
}
|
|
|
|
if (is_usage_update_needed &&
|
|
(usage_update_period_expired || is_initial_usage_update)) {
|
|
// Session list may have changed. Rebuild.
|
|
session_map_.GetSessionList(sessions);
|
|
|
|
for (CdmSessionList::iterator iter = sessions.begin();
|
|
iter != sessions.end(); ++iter) {
|
|
(*iter)->reset_usage_flags();
|
|
if ((*iter)->supports_usage_info() &&
|
|
(*iter)->has_provider_session_token()) {
|
|
(*iter)->UpdateUsageEntryInformation();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
CloseExpiredReleaseSessions();
|
|
}
|
|
|
|
void CdmEngine::OnKeyReleaseEvent(const CdmKeySetId& key_set_id) {
|
|
CdmSessionList sessions;
|
|
session_map_.GetSessionList(sessions);
|
|
while (!sessions.empty()) {
|
|
sessions.front()->OnKeyReleaseEvent(key_set_id);
|
|
sessions.pop_front();
|
|
}
|
|
}
|
|
|
|
CdmResponseType CdmEngine::ValidateServiceCertificate(const std::string& cert) {
|
|
ServiceCertificate certificate;
|
|
return certificate.Init(cert);
|
|
}
|
|
|
|
CdmResponseType CdmEngine::SetPlaybackId(const CdmSessionId& session_id,
|
|
const std::string& playback_id) {
|
|
LOGI("session_id = %s, playback_id = %s", IdToString(session_id),
|
|
IdToString(playback_id));
|
|
std::shared_ptr<CdmSession> session;
|
|
if (!session_map_.FindSession(session_id, &session)) {
|
|
LOGE("Session not found: session_id = %s", IdToString(session_id));
|
|
return SESSION_NOT_FOUND_23;
|
|
}
|
|
session->GetMetrics()->playback_id_.Record(playback_id);
|
|
return NO_ERROR;
|
|
}
|
|
|
|
std::string CdmEngine::MapHdcpVersion(CryptoSession::HdcpCapability version) {
|
|
switch (version) {
|
|
case HDCP_NONE:
|
|
return QUERY_VALUE_HDCP_NONE;
|
|
case HDCP_V1:
|
|
return QUERY_VALUE_HDCP_V1;
|
|
case HDCP_V2:
|
|
return QUERY_VALUE_HDCP_V2_0;
|
|
case HDCP_V2_1:
|
|
return QUERY_VALUE_HDCP_V2_1;
|
|
case HDCP_V2_2:
|
|
return QUERY_VALUE_HDCP_V2_2;
|
|
case HDCP_V2_3:
|
|
return QUERY_VALUE_HDCP_V2_3;
|
|
case HDCP_NO_DIGITAL_OUTPUT:
|
|
return QUERY_VALUE_HDCP_NO_DIGITAL_OUTPUT;
|
|
default:
|
|
return QUERY_VALUE_HDCP_LEVEL_UNKNOWN;
|
|
}
|
|
}
|
|
|
|
void CdmEngine::CloseExpiredReleaseSessions() {
|
|
const int64_t current_time = clock_.GetCurrentTime();
|
|
std::set<CdmSessionId> close_session_set;
|
|
{
|
|
std::unique_lock<std::mutex> lock(release_key_sets_lock_);
|
|
for (CdmReleaseKeySetMap::iterator iter = release_key_sets_.begin();
|
|
iter != release_key_sets_.end();) {
|
|
if (iter->second.second < current_time) {
|
|
close_session_set.insert(iter->second.first);
|
|
release_key_sets_.erase(iter++);
|
|
} else {
|
|
++iter;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (std::set<CdmSessionId>::iterator iter = close_session_set.begin();
|
|
iter != close_session_set.end(); ++iter) {
|
|
CloseSession(*iter);
|
|
}
|
|
}
|
|
|
|
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();
|
|
}
|
|
} // namespace wvcdm
|