Files
android/libwvdrmengine/cdm/core/src/cdm_engine.cpp
Rahul Frias e538c96131 Save and retrieve key information from licenses and usage records
[ Merge of http://go/wvgerrit/120512 ]

Wrapped DRM private keys are loaded when a key request is made or when
offline/usage sessions are restored. They were earlier loaded when a
session was opened.

For streaming sessions, key material will be fetched from the default
or legacy certificates and loaded when a key request is made.

For offline and usage sessions, key material may be retrieved from
license or usage records if available. If not available, information
associated with the legacy certificate will be loaded.

Certificate and wrapped keys are also written out when an offline
license or usage record is saved.

Bug: 169740403
Test: WV unit/integration tests
      WvCdmRequestLicenseTest.ProvisioningWithExpiringCertTest
      WvCdmRequestLicenseTest.StreamingWithExpiringCertTest
      WvCdmRequestLicenseTest.RestoreOfflineKeysWithExpiringCertTest
Change-Id: Ice0154c632170c46da171cbbb23a97380c610a98
2021-03-23 13:06:55 -07:00

1996 lines
67 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 "cdm_random.h"
#include "cdm_session.h"
#include "cdm_session_map.h"
#include "clock.h"
#include "device_files.h"
#include "file_store.h"
#include "log.h"
#include "properties.h"
#include "string_conversions.h"
#include "wv_cdm_constants.h"
#include "wv_cdm_event_listener.h"
namespace {
const uint64_t kReleaseSessionTimeToLive = 60; // seconds
const uint32_t kUpdateUsageInformationPeriod = 60; // seconds
wvcdm::CdmOfflineLicenseState MapDeviceFilesLicenseState(
wvcdm::DeviceFiles::LicenseState state) {
switch (state) {
case wvcdm::DeviceFiles::LicenseState::kLicenseStateActive:
return wvcdm::kLicenseStateActive;
case wvcdm::DeviceFiles::LicenseState::kLicenseStateReleasing:
return wvcdm::kLicenseStateReleasing;
default:
return wvcdm::kLicenseStateUnknown;
}
}
} // namespace
namespace wvcdm {
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) {
LOGI("Opening session");
if (!ValidateKeySystem(key_system)) {
LOGI("Invalid key system: %s", key_system.c_str());
return INVALID_KEY_SYSTEM;
}
if (!session_id && !forced_session_id) {
LOGE("No (forced/)session ID destination provided");
return PARAMETER_NULL;
}
if (forced_session_id) {
if (session_map_.Exists(*forced_session_id)) {
return DUPLICATE_SESSION_ID_SPECIFIED;
}
}
CloseExpiredReleaseSessions();
std::unique_ptr<CdmSession> new_session(
new CdmSession(file_system_, metrics_->AddSession()));
CdmResponseType sts =
new_session->Init(property_set, forced_session_id, event_listener);
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;
}
CdmSessionId id = new_session->session_id();
LOGI("New session ID: %s", id.c_str());
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("Opening key set session: key_set_id = %s", key_set_id.c_str());
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("Closing session: session_id = %s", session_id.c_str());
std::unique_lock<std::recursive_mutex> lock(session_map_lock_);
if (!session_map_.CloseSession(session_id)) {
LOGE("Session not found: %s", session_id.c_str());
return SESSION_NOT_FOUND_1;
}
metrics_->ConsolidateSessions();
return NO_ERROR;
}
CdmResponseType CdmEngine::CloseKeySetSession(const CdmKeySetId& key_set_id) {
LOGI("Closing key set session: key_set_id = %s", key_set_id.c_str());
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 ID not found: %s", key_set_id.c_str());
return KEYSET_ID_NOT_FOUND_1;
}
session_id = iter->second.first;
}
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("Generating key request: session_id = %s", session_id.c_str());
CdmSessionId id = session_id;
CdmResponseType sts;
// 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;
}
LOGI("Key set ID: %s", key_set_id.c_str());
if (!session_id.empty()) {
LOGE("Invalid session ID: %s", session_id.c_str());
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 ID not found: %s", key_set_id.c_str());
return KEYSET_ID_NOT_FOUND_2;
}
id = iter->second.first;
}
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(id, &session)) {
LOGE("Session ID not found: %s", id.c_str());
return SESSION_NOT_FOUND_2;
}
if (!key_request) {
LOGE("Output destination provided");
return PARAMETER_NULL;
}
key_request->message.clear();
if (license_type == kLicenseTypeRelease && !session->license_received()) {
int error_detail = NO_ERROR;
sts = session->RestoreOfflineSession(key_set_id, kLicenseTypeRelease,
&error_detail);
session->GetMetrics()->cdm_session_restore_offline_session_.Increment(
sts, error_detail);
if (sts != KEY_ADDED) {
LOGE("Key release restoration failed, status = %d",
static_cast<int>(sts));
return sts;
}
}
sts = session->GenerateKeyRequest(init_data, license_type, app_parameters,
key_request);
if (KEY_ADDED == sts) {
return sts;
} else if (KEY_MESSAGE != sts) {
LOGE("Key request generation failed, status = %d", 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(std::vector<uint8_t>(key_request->message.begin(),
key_request->message.end()))
.c_str());
return KEY_MESSAGE;
}
CdmResponseType CdmEngine::AddKey(const CdmSessionId& session_id,
const CdmKeyResponse& key_data,
CdmLicenseType* license_type,
CdmKeySetId* key_set_id) {
LOGI("Adding key: session_id = %s", session_id.c_str());
if (license_type == nullptr) {
LOGE("No license type provided");
return PARAMETER_NULL;
}
CdmSessionId id = session_id;
bool license_type_release = session_id.empty();
if (license_type_release) {
if (!key_set_id) {
LOGE("No key set ID provided");
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 ID not found: %s", key_set_id->c_str());
return KEYSET_ID_NOT_FOUND_3;
}
id = iter->second.first;
} else {
LOGD("key data: (%zu) %s", key_data.size(),
wvcdm::Base64SafeEncode(
std::vector<uint8_t>(key_data.begin(), key_data.end()))
.c_str());
}
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(id, &session)) {
LOGE("Session ID not found: %s", id.c_str());
return SESSION_NOT_FOUND_3;
}
if (key_data.empty()) {
LOGE("No key data");
return EMPTY_KEY_DATA_1;
}
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) {
if ((session->is_offline() || session->has_provider_session_token()) &&
!license_type_release) {
*key_set_id = session->key_set_id();
LOGI("Key set ID: %s", key_set_id->c_str());
} else {
key_set_id->clear();
}
}
switch (sts) {
case KEY_ADDED:
break;
case NEED_KEY:
LOGI("Service certificate loaded: No key added");
break;
default:
LOGE("Keys not added: status = %d", static_cast<int>(sts));
break;
}
return sts;
}
CdmResponseType CdmEngine::RestoreKey(const CdmSessionId& session_id,
const CdmKeySetId& key_set_id) {
LOGI("Restoring key: session_id = %s, key_set_id = %s", session_id.c_str(),
key_set_id.c_str());
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 ID not found: %s", session_id.c_str());
return SESSION_NOT_FOUND_4;
}
CdmResponseType sts;
int error_detail = NO_ERROR;
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: status = %d", static_cast<int>(sts));
}
return sts;
}
CdmResponseType CdmEngine::RemoveKeys(const CdmSessionId& session_id) {
LOGI("Removing keys: session_id = %s", session_id.c_str());
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(session_id, &session)) {
LOGE("Session ID not found: %s", session_id.c_str());
return SESSION_NOT_FOUND_5;
}
session->RemoveKeys();
return NO_ERROR;
}
CdmResponseType CdmEngine::RemoveLicense(const CdmSessionId& session_id) {
LOGI("Removing license: session_id = %s", session_id.c_str());
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(session_id, &session)) {
LOGE("Session ID not found: %s", session_id.c_str());
return SESSION_NOT_FOUND_19;
}
return session->RemoveLicense();
}
CdmResponseType CdmEngine::GenerateRenewalRequest(
const CdmSessionId& session_id, CdmKeyRequest* key_request) {
LOGI("Generating renewal request: session_id = %s", session_id.c_str());
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(session_id, &session)) {
LOGE("Session ID not found: %s", session_id.c_str());
return SESSION_NOT_FOUND_6;
}
if (!key_request) {
LOGE("No request destination");
return PARAMETER_NULL;
}
key_request->message.clear();
CdmResponseType sts = session->GenerateRenewalRequest(key_request);
if (KEY_MESSAGE != sts) {
LOGE("Key request gen. failed: status = %d", static_cast<int>(sts));
return sts;
}
return KEY_MESSAGE;
}
CdmResponseType CdmEngine::RenewKey(const CdmSessionId& session_id,
const CdmKeyResponse& key_data) {
LOGI("Renewing key: session_id = %s", session_id.c_str());
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(session_id, &session)) {
LOGE("Session ID not found: %s", session_id.c_str());
return SESSION_NOT_FOUND_7;
}
if (key_data.empty()) {
LOGE("No key data");
return EMPTY_KEY_DATA_2;
}
CdmResponseType sts;
M_TIME(sts = session->RenewKey(key_data), session->GetMetrics(),
cdm_session_renew_key_, sts);
if (KEY_ADDED != sts) {
LOGE("Keys not added: status = %d", static_cast<int>(sts));
return sts;
}
return KEY_ADDED;
}
CdmResponseType CdmEngine::SetSessionServiceCertificate(
const CdmSessionId& session_id, const std::string& service_certificate) {
LOGI("Setting service certificate: session_id = %s", session_id.c_str());
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(session_id, &session)) {
LOGE("Session ID not found: %s", session_id.c_str());
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) {
LOGI("Querying status");
std::unique_ptr<CryptoSession> crypto_session(
CryptoSession::MakeCryptoSession(metrics_->GetCryptoMetrics()));
CdmResponseType status;
if (!query_response) {
LOGE("No query response destination");
return PARAMETER_NULL;
}
// Add queries here, that can be answered before a session is opened
if (query_token == QUERY_KEY_SECURITY_LEVEL) {
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;
} else if (query_token == QUERY_KEY_CURRENT_HDCP_LEVEL ||
query_token == QUERY_KEY_MAX_HDCP_LEVEL) {
CryptoSession::HdcpCapability current_hdcp;
CryptoSession::HdcpCapability max_hdcp;
status = crypto_session->GetHdcpCapabilities(security_level, &current_hdcp,
&max_hdcp);
if (status != NO_ERROR) {
LOGW("GetHdcpCapabilities failed: status = %d", static_cast<int>(status));
return status;
}
*query_response = MapHdcpVersion(
query_token == QUERY_KEY_CURRENT_HDCP_LEVEL ? current_hdcp : max_hdcp);
return NO_ERROR;
} else if (query_token == QUERY_KEY_USAGE_SUPPORT) {
bool supports_usage_reporting;
const bool got_info = crypto_session->UsageInformationSupport(
security_level, &supports_usage_reporting);
if (!got_info) {
LOGW("UsageInformationSupport 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;
} else if (query_token == QUERY_KEY_NUMBER_OF_OPEN_SESSIONS) {
size_t number_of_open_sessions;
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;
} else if (query_token == QUERY_KEY_MAX_NUMBER_OF_SESSIONS) {
size_t maximum_number_of_sessions = 0;
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;
} else 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;
} else if (query_token == QUERY_KEY_CURRENT_SRM_VERSION) {
uint16_t current_srm_version;
status = crypto_session->GetSrmVersion(&current_srm_version);
switch (status) {
case NO_ERROR: {
*query_response = std::to_string(current_srm_version);
return NO_ERROR;
}
case NO_SRM_VERSION: {
// SRM is not supported or not applicable (ex. local display only).
*query_response = QUERY_VALUE_NONE;
return NO_ERROR;
}
default: {
LOGW("GetCurrentSRMVersion failed: status = %d",
static_cast<int>(status));
return status;
}
}
} else if (query_token == QUERY_KEY_SRM_UPDATE_SUPPORT) {
bool is_srm_update_supported = crypto_session->IsSrmUpdateSupported();
*query_response =
is_srm_update_supported ? QUERY_VALUE_TRUE : QUERY_VALUE_FALSE;
return NO_ERROR;
} else 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;
} else 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;
} else if (query_token == QUERY_KEY_OEMCRYPTO_BUILD_INFORMATION) {
if (!crypto_session->GetBuildInformation(security_level, query_response)) {
LOGW("GetBuildInformation failed");
return UNKNOWN_ERROR;
}
return NO_ERROR;
} else 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;
} else if (query_token == QUERY_KEY_PROVISIONING_MODEL) {
CdmClientTokenType token_type = kClientTokenUninitialized;
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 returns invalid token type: %d",
static_cast<int>(token_type));
return GET_PROVISIONING_METHOD_ERROR;
}
return NO_ERROR;
} else 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;
} else 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;
} else 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;
} else 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;
}
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 deviceId;
status = crypto_session->GetExternalDeviceUniqueId(&deviceId);
metrics_->GetCryptoMetrics()
->crypto_session_get_device_unique_id_.Increment(status);
if (status != NO_ERROR) return status;
*query_response = deviceId;
} else if (query_token == QUERY_KEY_SYSTEM_ID) {
uint32_t system_id;
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);
} else 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;
} else {
LOGW("Unknown status requested: query_token = %s", query_token.c_str());
return INVALID_QUERY_KEY;
}
return NO_ERROR;
}
CdmResponseType CdmEngine::QuerySessionStatus(const CdmSessionId& session_id,
CdmQueryMap* query_response) {
LOGI("Querying session status: session_id = %s", session_id.c_str());
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(session_id, &session)) {
LOGE("Session ID not found: %s", session_id.c_str());
return SESSION_NOT_FOUND_8;
}
return session->QueryStatus(query_response);
}
bool CdmEngine::IsReleaseSession(const CdmSessionId& session_id) {
LOGI("Check if release session: session_id = %s", session_id.c_str());
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(session_id, &session)) {
LOGE("Session ID not found: %s", session_id.c_str());
return false;
}
return session->is_release();
}
bool CdmEngine::IsOfflineSession(const CdmSessionId& session_id) {
LOGI("Check if offline session: session_id = %s", session_id.c_str());
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(session_id, &session)) {
LOGE("Session ID not found: %s", session_id.c_str());
return false;
}
return session->is_offline();
}
CdmResponseType CdmEngine::QueryKeyStatus(const CdmSessionId& session_id,
CdmQueryMap* query_response) {
LOGI("Querying key status: session_id = %s", session_id.c_str());
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(session_id, &session)) {
LOGE("Session ID not found: %s", session_id.c_str());
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("Querying allowed key usage: session_id = %s, key_id = %s",
session_id.c_str(), key_id.c_str());
if (!key_usage) {
LOGE("No response destination");
return PARAMETER_NULL;
}
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(session_id, &session)) {
LOGE("Session ID not found: %s", session_id.c_str());
return SESSION_NOT_FOUND_12;
}
return session->QueryKeyAllowedUsage(key_id, key_usage);
}
CdmResponseType CdmEngine::QueryKeyAllowedUsage(const std::string& key_id,
CdmKeyAllowedUsage* key_usage) {
LOGI("Querying allowed key useage (all sessions): key_id = %s",
key_id.c_str());
if (!key_usage) {
LOGE("No response destination");
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;
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("Querying OEMCrypto Session ID: session_id = %s", session_id.c_str());
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(session_id, &session)) {
LOGE("Session ID not found: %s", session_id.c_str());
return SESSION_NOT_FOUND_10;
}
return session->QueryOemCryptoSessionId(query_response);
}
bool CdmEngine::IsSecurityLevelSupported(CdmSecurityLevel level) {
LOGI("Checking if security level is supported: level = %d",
static_cast<int>(level));
metrics::CryptoMetrics alternate_crypto_metrics;
std::unique_ptr<CryptoSession> crypto_session(
CryptoSession::MakeCryptoSession(&alternate_crypto_metrics));
switch (level) {
case kSecurityLevelL1:
return crypto_session->GetSecurityLevel(kLevelDefault) ==
kSecurityLevelL1;
case kSecurityLevelL3:
return crypto_session->GetSecurityLevel(kLevel3) == kSecurityLevelL3;
default:
LOGE("Invalid security 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("Getting provisioning request");
if (!request) {
LOGE("Invalid output parameters: request is null");
return INVALID_PROVISIONING_REQUEST_PARAM_1;
}
if (!default_url) {
LOGE("Invalid output parameters: default_url is null");
return INVALID_PROVISIONING_REQUEST_PARAM_2;
}
// TODO(b/141705730): Remove usage entries on provisioning.
if (!cert_provisioning_) {
cert_provisioning_.reset(
new CertificateProvisioning(metrics_->GetCryptoMetrics()));
CdmResponseType status = cert_provisioning_->Init(service_certificate);
if (status != NO_ERROR) return status;
}
CdmResponseType ret = cert_provisioning_->GetProvisioningRequest(
requested_security_level, cert_type, cert_authority,
file_system_->origin(), spoid_, request, default_url);
if (ret != NO_ERROR) {
cert_provisioning_.reset(); // Release resources.
}
return ret;
}
/*
* 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("Handling provision response");
if (response.empty()) {
LOGE("Empty provisioning response");
cert_provisioning_.reset();
return EMPTY_PROVISIONING_RESPONSE;
}
if (cert == nullptr) {
LOGE("Invalid certificate destination");
cert_provisioning_.reset();
return INVALID_PROVISIONING_PARAMETERS_1;
}
if (wrapped_key == nullptr) {
LOGE("Invalid wrapped key destination");
cert_provisioning_.reset();
return INVALID_PROVISIONING_PARAMETERS_2;
}
if (!cert_provisioning_) {
// Certificate provisioning object has been released. Check if a concurrent
// provisioning attempt has succeeded before declaring failure.
std::unique_ptr<CryptoSession> crypto_session(
CryptoSession::MakeCryptoSession(metrics_->GetCryptoMetrics()));
CdmResponseType status;
M_TIME(status = crypto_session->Open(requested_security_level),
metrics_->GetCryptoMetrics(), crypto_session_open_, status,
requested_security_level);
if (NO_ERROR != status) {
LOGE("Provisioning object missing and crypto session open failed");
return EMPTY_PROVISIONING_CERTIFICATE_2;
}
CdmSecurityLevel security_level = crypto_session->GetSecurityLevel();
if (!IsProvisioned(security_level)) {
LOGE("Provisioning object missing");
return EMPTY_PROVISIONING_CERTIFICATE_1;
}
return NO_ERROR;
}
CdmResponseType ret = cert_provisioning_->HandleProvisioningResponse(
file_system_, response, cert, wrapped_key);
// Release resources only on success. It is possible that a provisioning
// attempt was made after this one was requested but before the response was
// received, which will cause this attempt to fail. Not releasing will
// allow for the possibility that the later attempt succeeds.
if (NO_ERROR == ret) cert_provisioning_.reset();
return ret;
}
bool CdmEngine::IsProvisioned(CdmSecurityLevel security_level) {
// To validate whether the given security level is provisioned, we attempt to
// initialize a CdmSession. This verifies the existence of a certificate and
// attempts to load it. If this fails, initialization will return an error.
UsagePropertySet property_set;
property_set.set_security_level(
security_level == kSecurityLevelL3 ? kLevel3 : kLevelDefault);
CdmSession session(file_system_, metrics_->AddSession());
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("Unprovisioning: security_level = %d", static_cast<int>(security_level));
// Devices with baked-in DRM certs cannot be reprovisioned and therefore must
// not be unprovisioned.
std::unique_ptr<CryptoSession> crypto_session(
CryptoSession::MakeCryptoSession(metrics_->GetCryptoMetrics()));
CdmClientTokenType token_type = kClientTokenUninitialized;
CdmResponseType res = crypto_session->GetProvisioningMethod(
security_level == kSecurityLevelL3 ? kLevel3 : kLevelDefault,
&token_type);
if (res != NO_ERROR) {
return res;
} else 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;
} else {
if (!handle.DeleteAllFiles()) {
LOGE("Unable to delete files");
return UNPROVISION_ERROR_3;
}
return NO_ERROR;
}
}
CdmResponseType CdmEngine::ListStoredLicenses(
CdmSecurityLevel security_level, std::vector<std::string>* key_set_ids) {
DeviceFiles handle(file_system_);
if (!key_set_ids) {
LOGE("No response destination");
return INVALID_PARAMETERS_ENG_22;
}
if (!handle.Init(security_level)) {
LOGE("Unable to initialize device files");
return LIST_LICENSE_ERROR_1;
}
if (!handle.ListLicenses(key_set_ids)) {
LOGE("ListLicenses call failed");
return LIST_LICENSE_ERROR_2;
}
return NO_ERROR;
}
CdmResponseType CdmEngine::ListUsageIds(
const std::string& app_id, CdmSecurityLevel security_level,
std::vector<std::string>* ksids,
std::vector<std::string>* provider_session_tokens) {
DeviceFiles handle(file_system_);
if (!ksids && !provider_session_tokens) {
LOGE("No response destination");
return INVALID_PARAMETERS_ENG_23;
}
if (!handle.Init(security_level)) {
LOGE("Unable to initialize device files");
return LIST_USAGE_ERROR_1;
}
if (!handle.ListUsageIds(app_id, ksids, provider_session_tokens)) {
LOGE("ListUsageIds call failed");
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("Deleting usage record: key_set_id = %s, app_id = %s",
key_set_id.c_str(), app_id.c_str());
std::string provider_session_token;
DeviceFiles handle(file_system_);
if (!handle.Init(security_level)) {
LOGE("Unable to initialize device files");
return DELETE_USAGE_ERROR_1;
}
if (!handle.GetProviderSessionToken(app_id, key_set_id,
&provider_session_token)) {
LOGE("GetProviderSessionToken failed");
return DELETE_USAGE_ERROR_2;
}
return RemoveUsageInfo(app_id, provider_session_token);
}
CdmResponseType CdmEngine::GetOfflineLicenseState(
const CdmKeySetId& key_set_id, CdmSecurityLevel security_level,
CdmOfflineLicenseState* license_state) {
DeviceFiles handle(file_system_);
if (!handle.Init(security_level)) {
LOGE("Cannot initialize device files");
return GET_OFFLINE_LICENSE_STATE_ERROR_1;
}
DeviceFiles::CdmLicenseData license_data;
DeviceFiles::ResponseType sub_error_code = DeviceFiles::kNoError;
if (handle.RetrieveLicense(key_set_id, &license_data, &sub_error_code)) {
*license_state = MapDeviceFilesLicenseState(license_data.state);
} else {
LOGE("Failed to retrieve license state: key_set_id = %s",
key_set_id.c_str());
return GET_OFFLINE_LICENSE_STATE_ERROR_2;
}
return NO_ERROR;
}
CdmResponseType CdmEngine::RemoveOfflineLicense(
const CdmKeySetId& key_set_id, CdmSecurityLevel security_level) {
UsagePropertySet property_set;
property_set.set_security_level(
security_level == kSecurityLevelL3 ? kLevel3 : kLevelDefault);
DeviceFiles handle(file_system_);
if (!handle.Init(security_level)) {
LOGE("Cannot initialize device files: security_level = %s",
security_level == kSecurityLevelL3 ? "L3" : "Default");
return REMOVE_OFFLINE_LICENSE_ERROR_1;
}
CdmResponseType sts = OpenKeySetSession(key_set_id, &property_set,
nullptr /* event listener */);
if (sts != NO_ERROR) {
LOGE("Failed to open key set session: status = %d", static_cast<int>(sts));
handle.DeleteLicense(key_set_id);
return sts;
}
CdmSessionId session_id;
CdmAppParameterMap dummy_app_params;
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 ID not found: %s", key_set_id.c_str());
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("Getting usage info: app_id = %s, ssid = %s", app_id.c_str(),
ssid.c_str());
if (!usage_property_set_) {
usage_property_set_.reset(new UsagePropertySet());
}
if (!usage_info) {
LOGE("No usage info destination");
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("Getting usage info: app_id = %s", app_id.c_str());
// Return a random usage report from a random security level
SecurityLevel security_level =
CdmRandom::RandomBool() ? kLevelDefault : kLevel3;
CdmResponseType status = UNKNOWN_ERROR;
if (!usage_info) {
LOGE("No usage info destination");
return PARAMETER_NULL;
}
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("Getting usage info: app_id = %s, security_level = %d", app_id.c_str(),
static_cast<int>(requested_security_level));
if (!usage_info) {
LOGE("No usage info destination");
return PARAMETER_NULL;
}
if (!usage_property_set_) {
usage_property_set_.reset(new UsagePropertySet());
}
usage_property_set_->set_security_level(requested_security_level);
usage_property_set_->set_app_id(app_id);
usage_session_.reset(new CdmSession(file_system_, metrics_->AddSession()));
CdmResponseType status = usage_session_->Init(usage_property_set_.get());
if (NO_ERROR != status) {
LOGE("Session init error");
return status;
}
DeviceFiles handle(file_system_);
if (!handle.Init(usage_session_->GetSecurityLevel())) {
LOGE("Unable to initialize device files");
return GET_USAGE_INFO_ERROR_3;
}
std::vector<DeviceFiles::CdmUsageData> usage_data;
if (!handle.RetrieveUsageInfo(DeviceFiles::GetUsageInfoFileName(app_id),
&usage_data)) {
LOGE("Unable to read usage information");
return GET_USAGE_INFO_ERROR_4;
}
if (usage_data.empty()) {
usage_info->clear();
return NO_ERROR;
}
const size_t index = CdmRandom::RandomInRange(usage_data.size() - 1);
status = usage_session_->RestoreUsageSession(usage_data[index], error_detail);
if (KEY_ADDED != status) {
// TODO(b/141704872): Make multiple attempts.
LOGE("RestoreUsageSession failed: index = %zu, status = %d", index,
static_cast<int>(status));
usage_info->clear();
return status;
}
CdmKeyRequest request;
status = usage_session_->GenerateReleaseRequest(&request);
usage_info->clear();
usage_info->push_back(request.message);
switch (status) {
case KEY_MESSAGE:
break;
case KEY_CANCELED: // usage information not present in
usage_session_->DeleteLicenseFile(); // OEMCrypto, delete and try again
usage_info->clear();
break;
default:
LOGE("GenerateReleaseRequest failed: status = %d",
static_cast<int>(status));
usage_info->clear();
break;
}
return status;
}
CdmResponseType CdmEngine::RemoveAllUsageInfo(
const std::string& app_id, CdmSecurityLevel cdm_security_level) {
LOGI("Removing all usage info: app_id = %s, security_level = %d",
app_id.c_str(), static_cast<int>(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)) {
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_->get_usage_support_type() == kUsageEntrySupport) {
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("Removing all usage info: app_id = %s", app_id.c_str());
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("Removing usage info: app_id = %s, pst = %s", app_id.c_str(),
provider_session_token.c_str());
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_->get_usage_support_type() == kUsageEntrySupport) {
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("Releasing usage info");
if (!usage_session_) {
LOGE("CDM session not initialized");
return RELEASE_USAGE_INFO_ERROR;
}
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("Loading usage session: key_set_id = %s", key_set_id.c_str());
// 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;
}
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(key_set_id, &session)) {
LOGE("Session ID not found: %s ", key_set_id.c_str());
return SESSION_NOT_FOUND_11;
}
if (!release_message) {
LOGE("No release message destination");
return PARAMETER_NULL;
}
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("Usage session error: status = %d", static_cast<int>(status));
return status;
}
CdmKeyRequest request;
status = session->GenerateReleaseRequest(&request);
*release_message = 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: Empty session ID");
return SESSION_NOT_FOUND_FOR_DECRYPT;
}
} else {
if (!session_map_.FindSession(session_id, &session)) {
LOGE("Session not found: session_id = %s", session_id.c_str());
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("No out buffer provided");
return PARAMETER_NULL;
}
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(session_id, &session)) {
LOGE("Session ID not found: %s", session_id.c_str());
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("No out buffer provided");
return PARAMETER_NULL;
}
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(session_id, &session)) {
LOGE("Session ID not found: %s", session_id.c_str());
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("No signature buffer provided");
return PARAMETER_NULL;
}
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(session_id, &session)) {
LOGE("Session ID not found: %s", session_id.c_str());
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 ID not found: %s", session_id.c_str());
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("Session ID was not provided");
return PARAMETER_NULL;
}
if (frame_number == nullptr) {
LOGE("Frame number was not provided");
return PARAMETER_NULL;
}
if (hash == nullptr) {
LOGE("Hash was not provided");
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;
}
std::vector<uint8_t> hash_vec = wvcdm::a2b_hex(tokens[2]);
if (hash_vec.empty()) {
LOGE("Malformed hash: %s", hash_string.c_str());
return INVALID_DECRYPT_HASH_FORMAT;
}
hash->assign(hash_vec.begin(), hash_vec.end());
return NO_ERROR;
}
CdmResponseType CdmEngine::SetDecryptHash(const CdmSessionId& session_id,
uint32_t frame_number,
const std::string& hash) {
LOGI("Setting decrypt hash: session_id = %s", session_id.c_str());
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("Getting decrypt hash error: session_id = %s", session_id.c_str());
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("No session ID destination provided");
return false;
}
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;
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)->get_usage_support_type() == kUsageEntrySupport &&
(*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("Setting session_id = %s playback_id = %s", session_id.c_str(),
playback_id.c_str());
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(session_id, &session)) {
LOGE("Session ID not found: %s", session_id.c_str());
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() {
int64_t current_time = clock_.GetCurrentTime();
std::set<CdmSessionId> close_session_set;
{
std::unique_lock<std::mutex> lock(release_key_sets_lock_);
for (CdmReleaseKeySetMap::iterator iter = release_key_sets_.begin();
iter != release_key_sets_.end();) {
if (iter->second.second < current_time) {
close_session_set.insert(iter->second.first);
release_key_sets_.erase(iter++);
} else {
++iter;
}
}
}
for (std::set<CdmSessionId>::iterator iter = close_session_set.begin();
iter != close_session_set.end(); ++iter) {
CloseSession(*iter);
}
}
} // namespace wvcdm