904 lines
35 KiB
C++
904 lines
35 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 "wv_content_decryption_module.h"
|
|
|
|
#include <algorithm>
|
|
#include <iterator>
|
|
|
|
#include "cdm_client_property_set.h"
|
|
#include "cdm_engine.h"
|
|
#include "cdm_engine_factory.h"
|
|
#include "initialization_data.h"
|
|
#include "license.h"
|
|
#include "log.h"
|
|
#include "properties.h"
|
|
#include "service_certificate.h"
|
|
#include "string_conversions.h"
|
|
#include "wv_cdm_constants.h"
|
|
#include "wv_cdm_event_listener.h"
|
|
#include "wv_metrics.pb.h"
|
|
|
|
namespace wvcdm {
|
|
|
|
using wvutil::FileSystem;
|
|
using wvutil::LoggingUidSetter;
|
|
|
|
namespace {
|
|
const int kCdmTimerDurationSeconds = 1;
|
|
|
|
// In the interest of limiting memory usage, only a limited number of
|
|
// the most recent metrics snapshots whould be stored.
|
|
constexpr size_t kMaxSavedMetricsSnapshotCount = 50;
|
|
|
|
// Value returned by time() if the operation fails.
|
|
constexpr time_t kTimeError = static_cast<time_t>(-1);
|
|
|
|
// Removes certain field from the provided CdmIdentifier as to make
|
|
// it better usable for tracking the same engine across different
|
|
// calls into the DRM plugin service.
|
|
CdmIdentifier ScrubIdentifierForMetrics(const CdmIdentifier& identifier) {
|
|
return CdmIdentifier{identifier.spoid, identifier.origin,
|
|
identifier.app_package_name, identifier.unique_id,
|
|
/* user_id */ wvutil::UNKNOWN_UID};
|
|
}
|
|
} // namespace
|
|
|
|
WvMetricsSnapshot::WvMetricsSnapshot(const CdmIdentifier& identifier,
|
|
drm_metrics::WvCdmMetrics&& metrics,
|
|
time_t ts)
|
|
: cdm_id_(ScrubIdentifierForMetrics(identifier)),
|
|
metrics_(std::move(metrics)),
|
|
timestamp_(ts) {}
|
|
|
|
WvMetricsSnapshot WvMetricsSnapshot::MakeSnapshot(
|
|
const CdmIdentifier& identifier, drm_metrics::WvCdmMetrics&& metrics) {
|
|
const time_t now = time(nullptr);
|
|
if (now == kTimeError) {
|
|
LOGW("Failed to get current time");
|
|
return WvMetricsSnapshot(identifier, std::move(metrics), 0);
|
|
}
|
|
return WvMetricsSnapshot(identifier, std::move(metrics), now);
|
|
}
|
|
|
|
std::string WvMetricsSnapshot::GetFormattedTimestamp() const {
|
|
if (timestamp_ == 0) return "<unset>";
|
|
struct tm time_info = {};
|
|
if (localtime_r(×tamp_, &time_info) == nullptr) {
|
|
LOGE("Failed to convert to local time");
|
|
return "<conversion error(" + std::to_string(timestamp_) + ")>";
|
|
}
|
|
char timestamp_buffer[64]; // asctime_r() requires at least 29 bytes.
|
|
memset(timestamp_buffer, 0, sizeof(timestamp_buffer));
|
|
if (asctime_r(&time_info, timestamp_buffer) == nullptr) {
|
|
LOGE("Failed to format localtime to ASC time");
|
|
return "<format error(" + std::to_string(timestamp_) + ")>";
|
|
}
|
|
std::string result(timestamp_buffer);
|
|
// The Linux manual illustrates asctime_r() as returning a value
|
|
// with a new line character at the end. However, it does not
|
|
// specify this in the description. This is to account for different
|
|
// behavior on different systems.
|
|
if (!result.empty() && result.back() == '\n') {
|
|
result.pop_back();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
WvMetricsSnapshotQueue::WvMetricsSnapshotQueue()
|
|
: capacity_(kMaxSavedMetricsSnapshotCount) {}
|
|
|
|
void WvMetricsSnapshotQueue::PushMetrics(WvMetricsSnapshot&& snapshot) {
|
|
std::unique_lock<std::mutex> lock(mutex_);
|
|
snapshots_.push_front(std::move(snapshot));
|
|
ReFit();
|
|
}
|
|
|
|
void WvMetricsSnapshotQueue::PushMetrics(const WvMetricsSnapshot& snapshot) {
|
|
std::unique_lock<std::mutex> lock(mutex_);
|
|
snapshots_.push_front(snapshot);
|
|
ReFit();
|
|
}
|
|
|
|
bool WvMetricsSnapshotQueue::GetAll(
|
|
std::vector<WvMetricsSnapshot>* snapshots) const {
|
|
if (snapshots == nullptr) return false;
|
|
std::unique_lock<std::mutex> lock(mutex_);
|
|
snapshots->assign(snapshots_.begin(), snapshots_.end());
|
|
return true;
|
|
}
|
|
|
|
void WvMetricsSnapshotQueue::Clear() {
|
|
std::unique_lock<std::mutex> lock(mutex_);
|
|
snapshots_.clear();
|
|
}
|
|
|
|
void WvMetricsSnapshotQueue::ReFit() {
|
|
if (capacity_ > 0 && snapshots_.size() > capacity_) {
|
|
snapshots_.resize(capacity_);
|
|
}
|
|
}
|
|
|
|
std::mutex WvContentDecryptionModule::session_sharing_id_generation_lock_;
|
|
|
|
WvContentDecryptionModule::WvContentDecryptionModule() {}
|
|
|
|
WvContentDecryptionModule::WvContentDecryptionModule(
|
|
WvMetricsSnapshotQueue* metrics_queue)
|
|
: saved_metrics_snapshots_(metrics_queue) {}
|
|
|
|
WvContentDecryptionModule::~WvContentDecryptionModule() {
|
|
CryptoSession::DisableDelayedTermination();
|
|
CloseAllCdms();
|
|
CryptoSession::TryTerminate();
|
|
DisableTimerAndWaitForExit();
|
|
}
|
|
|
|
bool WvContentDecryptionModule::IsSupported(const std::string& init_data_type) {
|
|
return InitializationData(init_data_type).is_supported();
|
|
}
|
|
|
|
bool WvContentDecryptionModule::IsCenc(const std::string& init_data_type) {
|
|
return InitializationData(init_data_type).is_cenc();
|
|
}
|
|
|
|
bool WvContentDecryptionModule::IsWebm(const std::string& init_data_type) {
|
|
return InitializationData(init_data_type).is_webm();
|
|
}
|
|
|
|
bool WvContentDecryptionModule::IsHls(const std::string& init_data_type) {
|
|
return InitializationData(init_data_type).is_hls();
|
|
}
|
|
|
|
bool WvContentDecryptionModule::IsAudio(const std::string& init_data_type) {
|
|
return InitializationData(init_data_type).is_audio();
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::OpenSession(
|
|
const CdmKeySystem& key_system, CdmClientPropertySet* property_set,
|
|
const CdmIdentifier& identifier, WvCdmEventListener* event_listener,
|
|
CdmSessionId* session_id) {
|
|
if (property_set && property_set->is_session_sharing_enabled()) {
|
|
std::unique_lock<std::mutex> auto_lock(session_sharing_id_generation_lock_);
|
|
if (property_set->session_sharing_id() == 0)
|
|
property_set->set_session_sharing_id(GenerateSessionSharingId());
|
|
}
|
|
|
|
CdmEngine* cdm_engine = EnsureCdmForIdentifier(identifier);
|
|
CdmResponseType sts = cdm_engine->OpenSession(key_system, property_set,
|
|
event_listener, session_id);
|
|
if (sts == NO_ERROR) {
|
|
std::unique_lock<std::mutex> auto_lock(cdms_lock_);
|
|
cdm_by_session_id_[*session_id] = cdm_engine;
|
|
}
|
|
return sts;
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::CloseSession(
|
|
const CdmSessionId& session_id) {
|
|
LOGV("Closing session ID: %s", session_id.c_str());
|
|
CdmEngine* cdm_engine = GetCdmForSessionId(session_id);
|
|
// TODO(rfrias): Avoid reusing the error codes from CdmEngine.
|
|
if (!cdm_engine) return CdmResponseType(SESSION_NOT_FOUND_1);
|
|
const CdmResponseType sts = cdm_engine->CloseSession(session_id);
|
|
if (sts == NO_ERROR) {
|
|
std::unique_lock<std::mutex> auto_lock(cdms_lock_);
|
|
cdm_by_session_id_.erase(session_id);
|
|
}
|
|
return sts;
|
|
}
|
|
|
|
bool WvContentDecryptionModule::IsOpenSession(const CdmSessionId& session_id) {
|
|
CdmEngine* cdm_engine = GetCdmForSessionId(session_id);
|
|
return cdm_engine && cdm_engine->IsOpenSession(session_id);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::GenerateKeyRequest(
|
|
const CdmSessionId& session_id, const CdmKeySetId& key_set_id,
|
|
const std::string& init_data_type, const CdmInitData& init_data,
|
|
const CdmLicenseType license_type, CdmAppParameterMap& app_parameters,
|
|
CdmClientPropertySet* property_set, const CdmIdentifier& identifier,
|
|
CdmKeyRequest* key_request) {
|
|
CdmEngine* cdm_engine = EnsureCdmForIdentifier(identifier);
|
|
CdmResponseType sts;
|
|
if (license_type == kLicenseTypeRelease) {
|
|
sts = cdm_engine->OpenKeySetSession(key_set_id, property_set, nullptr);
|
|
if (sts != NO_ERROR) return sts;
|
|
std::unique_lock<std::mutex> auto_lock(cdms_lock_);
|
|
cdm_by_session_id_[key_set_id] = cdm_engine;
|
|
}
|
|
|
|
const wvcdm::RequestedSecurityLevel requested_security_level =
|
|
property_set && property_set->security_level().compare(
|
|
wvcdm::QUERY_VALUE_SECURITY_LEVEL_L3) == 0
|
|
? wvcdm::kLevel3
|
|
: wvcdm::kLevelDefault;
|
|
|
|
std::string oec_version;
|
|
sts = cdm_engine->QueryStatus(requested_security_level,
|
|
QUERY_KEY_OEMCRYPTO_API_VERSION, &oec_version);
|
|
if (sts != NO_ERROR) {
|
|
return sts;
|
|
}
|
|
InitializationData initialization_data(init_data_type, init_data,
|
|
oec_version);
|
|
|
|
sts = cdm_engine->GenerateKeyRequest(session_id, key_set_id,
|
|
initialization_data, license_type,
|
|
app_parameters, key_request);
|
|
if (license_type == kLicenseTypeRelease && sts != KEY_MESSAGE) {
|
|
cdm_engine->CloseKeySetSession(key_set_id);
|
|
std::unique_lock<std::mutex> auto_lock(cdms_lock_);
|
|
cdm_by_session_id_.erase(key_set_id);
|
|
}
|
|
return sts;
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::AddKey(
|
|
const CdmSessionId& session_id, const CdmKeyResponse& key_data,
|
|
CdmKeySetId* key_set_id) {
|
|
CdmEngine* cdm_engine = session_id.empty() ? GetCdmForSessionId(*key_set_id)
|
|
: GetCdmForSessionId(session_id);
|
|
if (!cdm_engine) return CdmResponseType(SESSION_NOT_FOUND_3);
|
|
// Save key_set_id, as CDM will return an empty key_set_id on release
|
|
CdmKeySetId release_key_set_id;
|
|
if (session_id.empty() && key_set_id != nullptr) {
|
|
release_key_set_id = *key_set_id;
|
|
}
|
|
CdmResponseType sts;
|
|
CdmLicenseType license_type;
|
|
sts = cdm_engine->AddKey(session_id, key_data, &license_type, key_set_id);
|
|
// Empty session id indicates license type release.
|
|
if (sts == KEY_ADDED && session_id.empty()) {
|
|
cdm_engine->CloseKeySetSession(release_key_set_id);
|
|
std::unique_lock<std::mutex> auto_lock(cdms_lock_);
|
|
cdm_by_session_id_.erase(release_key_set_id);
|
|
}
|
|
return sts;
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::RestoreKey(
|
|
const CdmSessionId& session_id, const CdmKeySetId& key_set_id) {
|
|
CdmEngine* cdm_engine = GetCdmForSessionId(session_id);
|
|
if (!cdm_engine) return CdmResponseType(SESSION_NOT_FOUND_4);
|
|
CdmResponseType sts;
|
|
sts = cdm_engine->RestoreKey(session_id, key_set_id);
|
|
return sts;
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::RemoveKeys(
|
|
const CdmSessionId& session_id) {
|
|
CdmEngine* cdm_engine = GetCdmForSessionId(session_id);
|
|
if (!cdm_engine) return CdmResponseType(SESSION_NOT_FOUND_5);
|
|
CdmResponseType sts = cdm_engine->RemoveKeys(session_id);
|
|
return sts;
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::QueryStatus(
|
|
RequestedSecurityLevel security_level, const std::string& key,
|
|
std::string* value) {
|
|
CdmEngine* cdm_engine = EnsureCdmForIdentifier(kDefaultCdmIdentifier);
|
|
return cdm_engine->QueryStatus(security_level, key, value);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::QuerySessionStatus(
|
|
const CdmSessionId& session_id, CdmQueryMap* key_info) {
|
|
CdmEngine* cdm_engine = GetCdmForSessionId(session_id);
|
|
if (!cdm_engine) return CdmResponseType(SESSION_NOT_FOUND_8);
|
|
return cdm_engine->QuerySessionStatus(session_id, key_info);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::QueryKeyStatus(
|
|
const CdmSessionId& session_id, CdmQueryMap* key_info) {
|
|
CdmEngine* cdm_engine = GetCdmForSessionId(session_id);
|
|
if (!cdm_engine) return CdmResponseType(SESSION_NOT_FOUND_9);
|
|
CdmResponseType sts;
|
|
sts = cdm_engine->QueryKeyStatus(session_id, key_info);
|
|
return sts;
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::QueryOemCryptoSessionId(
|
|
const CdmSessionId& session_id, CdmQueryMap* response) {
|
|
CdmEngine* cdm_engine = GetCdmForSessionId(session_id);
|
|
if (!cdm_engine) return CdmResponseType(SESSION_NOT_FOUND_10);
|
|
return cdm_engine->QueryOemCryptoSessionId(session_id, response);
|
|
}
|
|
|
|
bool WvContentDecryptionModule::IsSecurityLevelSupported(
|
|
CdmSecurityLevel level) {
|
|
return CdmEngine::IsSecurityLevelSupported(level);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::GetProvisioningRequest(
|
|
CdmCertificateType cert_type, const std::string& cert_authority,
|
|
const CdmIdentifier& identifier, const std::string& service_certificate,
|
|
RequestedSecurityLevel requested_security_level,
|
|
CdmProvisioningRequest* request, std::string* default_url) {
|
|
CdmEngine* cdm_engine = EnsureCdmForIdentifier(identifier);
|
|
return cdm_engine->GetProvisioningRequest(
|
|
cert_type, cert_authority, service_certificate, requested_security_level,
|
|
request, default_url);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::HandleProvisioningResponse(
|
|
const CdmIdentifier& identifier, const CdmProvisioningResponse& response,
|
|
RequestedSecurityLevel requested_security_level, std::string* cert,
|
|
std::string* wrapped_key) {
|
|
CdmEngine* cdm_engine = EnsureCdmForIdentifier(identifier);
|
|
return cdm_engine->HandleProvisioningResponse(
|
|
response, requested_security_level, cert, wrapped_key);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::Unprovision(
|
|
CdmSecurityLevel level, const CdmIdentifier& identifier) {
|
|
CdmEngine* cdm_engine = EnsureCdmForIdentifier(identifier);
|
|
// Persistent state is deleted on unprovisioning. The L3 OEMCrypto device
|
|
// key may however remain in memory until |OEMCrypto_Terminate| is called.
|
|
// It is not regenerated until |OEMCrypto_Initialize| is called.
|
|
// Enable immediate OEMCrypto termination and re-initalization on
|
|
// unprovisioning.
|
|
CryptoSession::DisableDelayedTermination();
|
|
return cdm_engine->Unprovision(level);
|
|
}
|
|
|
|
bool WvContentDecryptionModule::IsProvisioned(CdmSecurityLevel security_level,
|
|
const std::string& origin,
|
|
const std::string& spoid,
|
|
bool atsc_mode_enabled) {
|
|
FileSystem file_system;
|
|
file_system.set_origin(origin);
|
|
file_system.set_identifier(spoid + origin);
|
|
DeviceFiles device_files(&file_system);
|
|
device_files.Init(security_level);
|
|
return device_files.HasCertificate(atsc_mode_enabled);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::GetUsageInfo(
|
|
const std::string& app_id, const CdmIdentifier& identifier,
|
|
CdmUsageReportList* usage_reports) {
|
|
if (usage_reports == nullptr) return CdmResponseType(PARAMETER_NULL);
|
|
CdmEngine* cdm_engine = EnsureCdmForIdentifier(identifier);
|
|
int error_detail = NO_ERROR;
|
|
CdmUsageReport usage_report;
|
|
// Doesn't actually retrieve all reports, just a single random one.
|
|
const CdmResponseType status =
|
|
cdm_engine->GetUsageInfo(app_id, &error_detail, &usage_report);
|
|
usage_reports->clear();
|
|
// Possible that no report is available, still a successful call.
|
|
if ((status == NO_ERROR || status == KEY_MESSAGE) && !usage_report.empty()) {
|
|
usage_reports->push_back(std::move(usage_report));
|
|
}
|
|
return status;
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::GetUsageInfo(
|
|
const std::string& app_id, const CdmSecureStopId& ssid,
|
|
const CdmIdentifier& identifier, CdmUsageReport* usage_report) {
|
|
CdmEngine* cdm_engine = EnsureCdmForIdentifier(identifier);
|
|
int error_detail = NO_ERROR;
|
|
return cdm_engine->GetUsageInfo(app_id, ssid, &error_detail, usage_report);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::RemoveAllUsageInfo(
|
|
const std::string& app_id, const CdmIdentifier& identifier) {
|
|
CdmEngine* cdm_engine = EnsureCdmForIdentifier(identifier);
|
|
return cdm_engine->RemoveAllUsageInfo(app_id);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::RemoveUsageInfo(
|
|
const std::string& app_id, const CdmIdentifier& identifier,
|
|
const CdmSecureStopId& secure_stop_id) {
|
|
CdmEngine* cdm_engine = EnsureCdmForIdentifier(identifier);
|
|
return cdm_engine->RemoveUsageInfo(app_id, secure_stop_id);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::ReleaseUsageInfo(
|
|
const CdmKeyResponse& message, const CdmIdentifier& identifier) {
|
|
CdmEngine* cdm_engine = EnsureCdmForIdentifier(identifier);
|
|
return cdm_engine->ReleaseUsageInfo(message);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::GetSecureStopIds(
|
|
const std::string& app_id, const CdmIdentifier& identifier,
|
|
std::vector<CdmSecureStopId>* ssids) {
|
|
if (ssids == nullptr) {
|
|
LOGE("Secure stop IDs destination not provided");
|
|
return CdmResponseType(PARAMETER_NULL);
|
|
}
|
|
|
|
CdmEngine* cdm_engine = EnsureCdmForIdentifier(identifier);
|
|
const CdmResponseType sts =
|
|
cdm_engine->ListUsageIds(app_id, kSecurityLevelL1, nullptr, ssids);
|
|
std::vector<CdmSecureStopId> ssids_l3;
|
|
const CdmResponseType sts_l3 =
|
|
cdm_engine->ListUsageIds(app_id, kSecurityLevelL3, nullptr, &ssids_l3);
|
|
ssids->insert(ssids->end(), ssids_l3.begin(), ssids_l3.end());
|
|
return sts_l3 != NO_ERROR ? sts_l3 : sts;
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::Decrypt(
|
|
const CdmSessionId& session_id, bool validate_key_id,
|
|
const CdmDecryptionParameters& parameters) {
|
|
return DecryptV16(session_id, validate_key_id,
|
|
CdmDecryptionParametersV16::from_v15(parameters));
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::DecryptV16(
|
|
const CdmSessionId& session_id, bool validate_key_id,
|
|
const CdmDecryptionParametersV16& parameters) {
|
|
// First find the CdmEngine that has the given session_id. If we are using
|
|
// key sharing, the shared session will still be in the same CdmEngine.
|
|
CdmEngine* cdm_engine = GetCdmForSessionId(session_id);
|
|
if (!cdm_engine) {
|
|
LOGE("Decrypt session ID not found: %s", session_id.c_str());
|
|
return CdmResponseType(SESSION_NOT_FOUND_18);
|
|
}
|
|
|
|
CdmSessionId local_session_id = session_id;
|
|
if (validate_key_id && Properties::GetSessionSharingId(session_id) != 0) {
|
|
bool status =
|
|
cdm_engine->FindSessionForKey(parameters.key_id, &local_session_id);
|
|
if (!status) {
|
|
bool is_any_protected = std::any_of(
|
|
std::begin(parameters.samples), std::end(parameters.samples),
|
|
[](const CdmDecryptionSample& sample) -> bool {
|
|
return std::any_of(
|
|
std::begin(sample.subsamples), std::end(sample.subsamples),
|
|
[](const CdmDecryptionSubsample& subsample) -> bool {
|
|
return subsample.protected_bytes > 0;
|
|
});
|
|
});
|
|
|
|
// A key does not need to be loaded if
|
|
// (a) the content consists of one or more samples and is entirely
|
|
// clear data
|
|
// (b) (legacy) the content consists of clear lead/frame in a single
|
|
// subsample. In earlier releases content was presented for decryption
|
|
// as individual subsamples. If the clear frame is broken up across
|
|
// multiple subsamples decryption, requests should be rejected
|
|
// (b/110251447)
|
|
// TODO(b/149524614): Remove support for the legacy case
|
|
if (is_any_protected ||
|
|
(parameters.observe_legacy_fields && parameters.samples.size() == 1 &&
|
|
parameters.samples[0].subsamples.size() == 1 &&
|
|
!(parameters.samples[0].subsamples[0].flags &
|
|
OEMCrypto_FirstSubsample &&
|
|
parameters.samples[0].subsamples[0].flags &
|
|
OEMCrypto_LastSubsample))) {
|
|
return CdmResponseType(KEY_NOT_FOUND_IN_SESSION);
|
|
}
|
|
}
|
|
}
|
|
return cdm_engine->DecryptV16(local_session_id, parameters);
|
|
}
|
|
|
|
void WvContentDecryptionModule::NotifyResolution(const CdmSessionId& session_id,
|
|
uint32_t width,
|
|
uint32_t height) {
|
|
CdmEngine* cdm_engine = GetCdmForSessionId(session_id);
|
|
if (!cdm_engine) return;
|
|
cdm_engine->NotifyResolution(session_id, width, height);
|
|
}
|
|
|
|
bool WvContentDecryptionModule::IsValidServiceCertificate(
|
|
const std::string& certificate) {
|
|
ServiceCertificate cert;
|
|
CdmResponseType status = cert.Init(certificate);
|
|
if (status != NO_ERROR) return false;
|
|
return cert.has_certificate();
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::GetCurrentMetrics(
|
|
const CdmIdentifier& identifier, drm_metrics::WvCdmMetrics* metrics) {
|
|
if (!metrics) {
|
|
return CdmResponseType(PARAMETER_NULL);
|
|
}
|
|
std::unique_lock<std::mutex> auto_lock(cdms_lock_);
|
|
return GetCurrentMetricsInternal(identifier, metrics);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::GetAllCurrentMetricsSnapshots(
|
|
std::vector<WvMetricsSnapshot>* snapshots, bool* full_list_returned) {
|
|
if (!snapshots || !full_list_returned) {
|
|
return CdmResponseType(PARAMETER_NULL);
|
|
}
|
|
snapshots->clear();
|
|
std::unique_lock<std::mutex> auto_lock(cdms_lock_);
|
|
for (const auto& cdm_info : cdms_) {
|
|
const CdmIdentifier& identifier = cdm_info.first;
|
|
drm_metrics::WvCdmMetrics metric;
|
|
const CdmResponseType status =
|
|
GetCurrentMetricsInternal(identifier, &metric);
|
|
if (status == NO_ERROR) {
|
|
snapshots->push_back(
|
|
WvMetricsSnapshot::MakeSnapshot(identifier, std::move(metric)));
|
|
} else {
|
|
LOGD("Failed to get metrics: cdm_identifier = %u, error = %s",
|
|
identifier.unique_id, status.ToString().c_str());
|
|
}
|
|
}
|
|
// With no streaming activities, |cdms_| size would be zero,
|
|
// treat it as a non full list in that case.
|
|
*full_list_returned = !cdms_.empty() && snapshots->size() == cdms_.size();
|
|
|
|
// We only return error if no metrics is returned when cdms_ is not empty.
|
|
// - metrics && cdms_ sizes can both be zero, returns NO_ERROR
|
|
// - metrics size <= cdms_, returns NO_ERROR
|
|
// - metrics is empty, but cdms_ is not, returns UNKNOWN_ERROR
|
|
return (snapshots->empty() && !cdms_.empty()) ? CdmResponseType(UNKNOWN_ERROR)
|
|
: CdmResponseType(NO_ERROR);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::GetAllSavedMetricsSnapshots(
|
|
std::vector<WvMetricsSnapshot>* snapshots) {
|
|
if (snapshots == nullptr) {
|
|
return CdmResponseType(PARAMETER_NULL);
|
|
}
|
|
if (saved_metrics_snapshots_ == nullptr) {
|
|
snapshots->clear();
|
|
// Storing the metrics might not be available in some cases.
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
// This has the potential to be an expensive operation. However,
|
|
// this function is only used during debug reporting.
|
|
if (!saved_metrics_snapshots_->GetAll(snapshots)) {
|
|
return CdmResponseType(UNKNOWN_ERROR);
|
|
}
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::GetCurrentMetricsInternal(
|
|
const CdmIdentifier& identifier, drm_metrics::WvCdmMetrics* metrics) {
|
|
// Note: Caller should lock before calling.
|
|
auto it = cdms_.find(identifier);
|
|
if (it == cdms_.end()) {
|
|
LOGE("Cdm Identifier not found");
|
|
// TODO(blueeyes): Add a better error.
|
|
return CdmResponseType(UNKNOWN_ERROR);
|
|
}
|
|
return it->second.cdm_engine->GetMetricsSnapshot(metrics)
|
|
? CdmResponseType(NO_ERROR)
|
|
: CdmResponseType(UNKNOWN_ERROR);
|
|
}
|
|
|
|
void WvContentDecryptionModule::SaveMetrics(
|
|
const CdmIdentifier& identifier, drm_metrics::WvCdmMetrics&& metrics) {
|
|
if (saved_metrics_snapshots_ == nullptr) return;
|
|
// The DRM plugin opens and closes many CDMs using the default
|
|
// identifier. These metrics are not needed when checking the
|
|
// metric history.
|
|
if (identifier.IsEquivalentToDefault()) return;
|
|
saved_metrics_snapshots_->PushMetrics(
|
|
WvMetricsSnapshot::MakeSnapshot(identifier, std::move(metrics)));
|
|
}
|
|
|
|
WvContentDecryptionModule::CdmInfo::CdmInfo()
|
|
: cdm_engine(CdmEngineFactory::CreateCdmEngine(&file_system)) {}
|
|
|
|
CdmEngine* WvContentDecryptionModule::EnsureCdmForIdentifier(
|
|
const CdmIdentifier& identifier) {
|
|
CdmEngine* cdm_engine;
|
|
bool enable_timer = false;
|
|
{
|
|
std::unique_lock<std::mutex> auto_lock(cdms_lock_);
|
|
if (cdms_.size() == 0) enable_timer = true;
|
|
if (cdms_.find(identifier) == cdms_.end()) {
|
|
// Accessing the map entry will create a new instance using the default
|
|
// constructor. We then need to provide it with two pieces of info: The
|
|
// origin provided by the app and an identifier that uniquely identifies
|
|
// this CDM. We concatenate all pieces of the CdmIdentifier in order to
|
|
// create an ID that is unique to that identifier.
|
|
cdms_[identifier].file_system.set_origin(identifier.origin);
|
|
cdms_[identifier].file_system.set_identifier(identifier.spoid +
|
|
identifier.origin);
|
|
cdms_[identifier].cdm_engine->SetAppPackageName(
|
|
identifier.app_package_name);
|
|
cdms_[identifier].cdm_engine->SetSpoid(identifier.spoid);
|
|
cdms_[identifier].cdm_engine->SetUserId(identifier.user_id);
|
|
}
|
|
cdm_engine = cdms_[identifier].cdm_engine.get();
|
|
}
|
|
// Do not enable timer while holding on to the |cdms_lock_|
|
|
if (enable_timer) EnableTimer();
|
|
|
|
return cdm_engine;
|
|
}
|
|
|
|
CdmEngine* WvContentDecryptionModule::GetCdmForSessionId(
|
|
const std::string& session_id) {
|
|
std::unique_lock<std::mutex> auto_lock(cdms_lock_);
|
|
// Use find to avoid creating empty entries when not found.
|
|
auto it = cdm_by_session_id_.find(session_id);
|
|
if (it == cdm_by_session_id_.end()) return nullptr;
|
|
return it->second;
|
|
}
|
|
|
|
void WvContentDecryptionModule::CloseAllCdms() {
|
|
std::unique_lock<std::mutex> auto_lock(cdms_lock_);
|
|
for (const auto& cdm_info : cdms_) {
|
|
const CdmIdentifier& identifier = cdm_info.first;
|
|
drm_metrics::WvCdmMetrics metrics;
|
|
const CdmResponseType status =
|
|
GetCurrentMetricsInternal(identifier, &metrics);
|
|
if (status == NO_ERROR) {
|
|
SaveMetrics(identifier, std::move(metrics));
|
|
} else {
|
|
LOGD("CloseAllCdms failed to save metrics");
|
|
}
|
|
}
|
|
cdm_by_session_id_.clear();
|
|
cdms_.clear();
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::CloseCdm(
|
|
const CdmIdentifier& cdm_identifier) {
|
|
std::unique_lock<std::mutex> auto_lock(cdms_lock_);
|
|
auto cdm_it = cdms_.find(cdm_identifier);
|
|
if (cdm_it == cdms_.end()) {
|
|
LOGE("Cdm Identifier not found");
|
|
// TODO(blueeyes): Create a better error.
|
|
return CdmResponseType(UNKNOWN_ERROR);
|
|
}
|
|
CdmEngine* cdm_engine = cdm_it->second.cdm_engine.get();
|
|
// Save metrics snapshot.
|
|
drm_metrics::WvCdmMetrics metrics;
|
|
const bool success = cdm_engine->GetMetricsSnapshot(&metrics);
|
|
if (success) {
|
|
SaveMetrics(cdm_identifier, std::move(metrics));
|
|
} else {
|
|
LOGW("Failed to get metrics snapshot: unique_id = %" PRIu32,
|
|
cdm_identifier.unique_id);
|
|
}
|
|
// Remove any sessions that point to this engine.
|
|
for (auto session_it = cdm_by_session_id_.begin();
|
|
session_it != cdm_by_session_id_.end();) {
|
|
if (session_it->second == cdm_engine) {
|
|
session_it = cdm_by_session_id_.erase(session_it);
|
|
} else {
|
|
++session_it;
|
|
}
|
|
}
|
|
cdms_.erase(cdm_it);
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::SetDebugIgnoreKeyboxCount(
|
|
uint32_t count) {
|
|
return CdmEngine::SetDebugIgnoreKeyboxCount(count);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::SetAllowTestKeybox(bool allow) {
|
|
return CdmEngine::SetAllowTestKeybox(allow);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::SetDecryptHash(
|
|
const std::string& hash_data, CdmSessionId* id) {
|
|
if (id == nullptr) {
|
|
LOGE("Cdm session ID not provided");
|
|
return CdmResponseType(PARAMETER_NULL);
|
|
}
|
|
|
|
uint32_t frame_number;
|
|
std::string hash;
|
|
CdmResponseType res =
|
|
CdmEngine::ParseDecryptHashString(hash_data, id, &frame_number, &hash);
|
|
|
|
if (res != NO_ERROR) return res;
|
|
|
|
CdmEngine* cdm_engine = GetCdmForSessionId(*id);
|
|
|
|
if (!cdm_engine) {
|
|
LOGE("Unable to find CdmEngine");
|
|
return CdmResponseType(SESSION_NOT_FOUND_20);
|
|
}
|
|
|
|
res = cdm_engine->SetDecryptHash(*id, frame_number, hash);
|
|
return res;
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::GetDecryptHashError(
|
|
const CdmSessionId& session_id, std::string* hash_error_string) {
|
|
CdmEngine* cdm_engine = GetCdmForSessionId(session_id);
|
|
|
|
if (!cdm_engine) {
|
|
LOGE("Unable to find CdmEngine");
|
|
return CdmResponseType(SESSION_NOT_FOUND_20);
|
|
}
|
|
return cdm_engine->GetDecryptHashError(session_id, hash_error_string);
|
|
}
|
|
|
|
void WvContentDecryptionModule::EnableTimer() {
|
|
std::unique_lock<std::mutex> auto_lock(timer_lock_);
|
|
if (!timer_.IsRunning()) timer_.Start(this, kCdmTimerDurationSeconds);
|
|
}
|
|
|
|
void WvContentDecryptionModule::DisableTimer() {
|
|
std::unique_lock<std::mutex> auto_lock(timer_lock_);
|
|
if (timer_.IsRunning()) {
|
|
timer_.Stop(false);
|
|
}
|
|
}
|
|
|
|
void WvContentDecryptionModule::DisableTimerAndWaitForExit() {
|
|
std::unique_lock<std::mutex> auto_lock(timer_lock_);
|
|
if (timer_.IsRunning()) {
|
|
timer_.Stop(true);
|
|
}
|
|
}
|
|
|
|
void WvContentDecryptionModule::OnTimerEvent() {
|
|
bool disable_timer = false;
|
|
{
|
|
std::unique_lock<std::mutex> auto_lock(cdms_lock_);
|
|
for (auto it = cdms_.begin(); it != cdms_.end(); ++it) {
|
|
LoggingUidSetter set_uid(it->first.user_id);
|
|
it->second.cdm_engine->OnTimerEvent();
|
|
}
|
|
if (cdms_.empty()) {
|
|
// The following code cannot be attributed to any app uid.
|
|
LoggingUidSetter set_uid(wvutil::UNKNOWN_UID);
|
|
if (CryptoSession::TryTerminate()) {
|
|
// If CryptoSession is in a state to be terminated, try acquiring the
|
|
// |timer_lock_| before deciding whether to disable the timer. If the
|
|
// lock cannot be acquired, there is no need to disable the timer.
|
|
// The |timer_lock_| is either being held by
|
|
// * EnableTimer - This does not appear possible, but if it did
|
|
// happen |OnTimerEvent| will be called again. The timer can be
|
|
// disabled during a future call.
|
|
// * DisableTimer - If so, allow that call to disable the timer. No
|
|
// need to call to disable it here.
|
|
// * DisableTimerAndWaitForExit - If so, allow that call to disable
|
|
// the timer. No need to call to disable it here. Note that
|
|
// |DisableTimerAndWaitForExit| will try to stop the timer but
|
|
// wait for it to exit. This might kick off the timer thread one
|
|
// last time, which will call into OnTimerEvent. Calling
|
|
// |DisableTimer| here will result in deadlock.
|
|
if (timer_lock_.try_lock()) {
|
|
disable_timer = true;
|
|
timer_lock_.unlock();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Release |cdms_lock_| before attempting to disable the timer
|
|
if (disable_timer) {
|
|
DisableTimer();
|
|
}
|
|
}
|
|
|
|
uint32_t WvContentDecryptionModule::GenerateSessionSharingId() {
|
|
static int next_session_sharing_id = 0;
|
|
return ++next_session_sharing_id;
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::ListStoredLicenses(
|
|
CdmSecurityLevel security_level, const CdmIdentifier& identifier,
|
|
std::vector<CdmKeySetId>* key_set_ids) {
|
|
CdmEngine* cdm_engine = EnsureCdmForIdentifier(identifier);
|
|
return cdm_engine->ListStoredLicenses(security_level, key_set_ids);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::GetOfflineLicenseState(
|
|
const CdmKeySetId& key_set_id, CdmSecurityLevel security_level,
|
|
const CdmIdentifier& identifier, CdmOfflineLicenseState* license_state) {
|
|
CdmEngine* cdm_engine = EnsureCdmForIdentifier(identifier);
|
|
return cdm_engine->GetOfflineLicenseState(key_set_id, security_level,
|
|
license_state);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::RemoveOfflineLicense(
|
|
const CdmKeySetId& key_set_id, CdmSecurityLevel security_level,
|
|
const CdmIdentifier& identifier) {
|
|
CdmEngine* cdm_engine = EnsureCdmForIdentifier(identifier);
|
|
return cdm_engine->RemoveOfflineLicense(key_set_id, security_level);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::SetPlaybackId(
|
|
const CdmSessionId& session_id, const std::string& playback_id) {
|
|
LOGV("Setting session ID %s playback ID %s", session_id.c_str(),
|
|
playback_id.c_str());
|
|
CdmEngine* cdm_engine = GetCdmForSessionId(session_id);
|
|
if (!cdm_engine) return CdmResponseType(SESSION_NOT_FOUND_23);
|
|
return cdm_engine->SetPlaybackId(session_id, playback_id);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::GetSessionUserId(
|
|
const CdmSessionId& session_id, uint32_t* user_id) {
|
|
if (!user_id) return CdmResponseType(PARAMETER_NULL);
|
|
CdmEngine* cdm_engine = GetCdmForSessionId(session_id);
|
|
if (!cdm_engine) return CdmResponseType(SESSION_NOT_FOUND_23);
|
|
*user_id = cdm_engine->GetUserId();
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::StoreAtscLicense(
|
|
const CdmIdentifier& identifier,
|
|
RequestedSecurityLevel requested_security_level,
|
|
const CdmKeySetId& key_set_id, const std::string& serialized_license_data) {
|
|
CdmEngine* cdm_engine = EnsureCdmForIdentifier(identifier);
|
|
return cdm_engine->StoreAtscLicense(requested_security_level, key_set_id,
|
|
serialized_license_data);
|
|
}
|
|
|
|
bool WvContentDecryptionModule::SetDefaultOtaKeyboxFallbackDurationRules() {
|
|
CdmEngine* cdm_engine = EnsureCdmForIdentifier(kDefaultCdmIdentifier);
|
|
if (!cdm_engine) return false;
|
|
cdm_engine->SetDefaultOtaKeyboxFallbackDurationRules();
|
|
return true;
|
|
}
|
|
|
|
bool WvContentDecryptionModule::SetFastOtaKeyboxFallbackDurationRules() {
|
|
CdmEngine* cdm_engine = EnsureCdmForIdentifier(kDefaultCdmIdentifier);
|
|
if (!cdm_engine) return false;
|
|
cdm_engine->SetFastOtaKeyboxFallbackDurationRules();
|
|
return true;
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::GenericEncrypt(
|
|
const CdmSessionId& session_id, const KeyId& key_id,
|
|
const std::string& input, const std::string& iv,
|
|
CdmEncryptionAlgorithm algorithm, std::string* output) {
|
|
if (output == nullptr) {
|
|
LOGE("Output parameter |output| is null");
|
|
return CdmResponseType(PARAMETER_NULL);
|
|
}
|
|
CdmEngine* cdm_engine = GetCdmForSessionId(session_id);
|
|
if (cdm_engine == nullptr) {
|
|
LOGW("Could not find session: sid = %s", wvcdm::IdToString(session_id));
|
|
return CdmResponseType(SESSION_NOT_FOUND_GENERIC_CRYPTO);
|
|
}
|
|
return cdm_engine->GenericEncrypt(session_id, input, key_id, iv, algorithm,
|
|
output);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::GenericDecrypt(
|
|
const CdmSessionId& session_id, const KeyId& key_id,
|
|
const std::string& input, const std::string& iv,
|
|
CdmEncryptionAlgorithm algorithm, std::string* output) {
|
|
if (output == nullptr) {
|
|
LOGE("Output parameter |output| is null");
|
|
return CdmResponseType(PARAMETER_NULL);
|
|
}
|
|
CdmEngine* cdm_engine = GetCdmForSessionId(session_id);
|
|
if (cdm_engine == nullptr) {
|
|
LOGW("Could not find session: sid = %s", wvcdm::IdToString(session_id));
|
|
return CdmResponseType(SESSION_NOT_FOUND_GENERIC_CRYPTO);
|
|
}
|
|
return cdm_engine->GenericDecrypt(session_id, input, key_id, iv, algorithm,
|
|
output);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::GenericSign(
|
|
const CdmSessionId& session_id, const KeyId& key_id,
|
|
const std::string& input, CdmSigningAlgorithm algorithm,
|
|
std::string* signature) {
|
|
if (signature == nullptr) {
|
|
LOGE("Output parameter |signature| is null");
|
|
return CdmResponseType(PARAMETER_NULL);
|
|
}
|
|
CdmEngine* cdm_engine = GetCdmForSessionId(session_id);
|
|
if (cdm_engine == nullptr) {
|
|
LOGW("Could not find session: sid = %s", wvcdm::IdToString(session_id));
|
|
return CdmResponseType(SESSION_NOT_FOUND_GENERIC_CRYPTO);
|
|
}
|
|
return cdm_engine->GenericSign(session_id, input, key_id, algorithm,
|
|
signature);
|
|
}
|
|
|
|
CdmResponseType WvContentDecryptionModule::GenericVerify(
|
|
const CdmSessionId& session_id, const KeyId& key_id,
|
|
const std::string& input, CdmSigningAlgorithm algorithm,
|
|
const std::string& signature) {
|
|
CdmEngine* cdm_engine = GetCdmForSessionId(session_id);
|
|
if (cdm_engine == nullptr) {
|
|
LOGW("Could not find session: sid = %s", wvcdm::IdToString(session_id));
|
|
return CdmResponseType(SESSION_NOT_FOUND_GENERIC_CRYPTO);
|
|
}
|
|
return cdm_engine->GenericVerify(session_id, input, key_id, algorithm,
|
|
signature);
|
|
}
|
|
|
|
} // namespace wvcdm
|