Files
ce_cdm/cdm/src/cdm.cpp
2020-10-09 16:08:56 -07:00

1844 lines
60 KiB
C++

// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#include "cdm.h"
#include <assert.h>
#include <limits.h> // LLONG_MAX
#include <string.h> // memcpy
#include <algorithm>
#include <iterator>
#include <memory>
#include <vector>
// core:
#include "cdm_client_property_set.h"
#include "cdm_engine.h"
#include "cdm_engine_factory.h"
#include "clock.h"
#include "crypto_session.h"
#include "device_files.h"
#include "file_store.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_cdm_types.h"
#include "wv_metrics.pb.h"
// CE:
#include "cdm_version.h"
#include "properties_ce.h"
namespace widevine {
using namespace wvcdm;
namespace {
constexpr const char* kNoSandboxId = "";
const int64_t kPolicyTimerDurationMilliseconds = 5000;
void* const kPolicyTimerContext = nullptr;
struct HostType {
Cdm::IStorage* storage;
Cdm::IClock* clock;
Cdm::ITimer* timer;
FileSystem* file_system;
bool initialized;
HostType()
: storage(nullptr),
clock(nullptr),
timer(nullptr),
file_system(nullptr),
initialized(false) {}
} host;
class PropertySet final : public CdmClientPropertySet {
public:
PropertySet() : use_privacy_mode_(false) {}
~PropertySet() override {}
const std::string& security_level() const override {
// Unused on CE platforms. Used by Android to switch to L3.
return empty_string_;
}
void set_use_privacy_mode(bool use) { use_privacy_mode_ = use; }
bool use_privacy_mode() const override { return use_privacy_mode_; }
const std::string& service_certificate() const override {
return licensing_service_certificate_;
}
void set_service_certificate(const std::string& cert) override {
licensing_service_certificate_ = cert;
}
bool is_session_sharing_enabled() const override {
// Unused on CE platforms.
return true;
}
uint32_t session_sharing_id() const override {
// Unused on CE platforms.
return 1;
}
void set_session_sharing_id(uint32_t) override {
// Unused on CE platforms.
return;
}
const std::string& app_id() const override {
// Unused on CE platforms.
return empty_string_;
}
bool use_atsc_mode() const override { return false; }
private:
bool use_privacy_mode_;
std::string licensing_service_certificate_;
// This is empty, but g++ 4.8 will not allow app_id() to return a string
// literal as a const reference to std::string.
const std::string empty_string_;
};
// A wrapper for another IStorage instance that intercepts attempts to write to
// that storage and blocks them. Acts as a last-ditch safeguard against the CDM
// trying to write erroneously to filesystems where it is critical that we never
// overwrite or delete the files. Specifically, preloaded files on ATSC 3.
//
// TODO(b/148693106): Right now, this wrapper fails writes silently because
// there are known bugs where the CDM will still try to write to it. Once these
// bugs are resolved, we can change this wrapper to return errors on write
// attempts. The wrapper itself will still be necessary, in order to prevent any
// future bugs from corrupting ATSC files.
class ReadOnlyStorage final : public Cdm::IStorage {
public:
explicit ReadOnlyStorage(Cdm::IStorage* storage) : storage_(storage) {}
~ReadOnlyStorage() override {}
bool read(const std::string& name, std::string* data) override {
return storage_->read(name, data);
}
bool write(const std::string& name, const std::string& data) override {
// TODO(b/148693106): Once we have resolved the bugs causing the CDM to
// erroneously write to read-only files, change this to an error instead of
// a silent failure.
return true;
}
bool exists(const std::string& name) override {
return storage_->exists(name);
}
bool remove(const std::string& name) override {
// TODO(b/148693106): Once we have resolved the bugs causing the CDM to
// erroneously write to read-only files, change this to an error instead of
// a silent failure.
return true;
}
int32_t size(const std::string& name) override {
return storage_->size(name);
}
bool list(std::vector<std::string>* file_names) override {
return storage_->list(file_names);
}
private:
Cdm::IStorage* storage_; // Lifetime is managed by the caller
};
// This class wraps a raw C array passed as a pointer and length in the
// appropriate interface to be used with functions that expect a C++11 range.
template <typename T>
class CArrayRangeView {
public:
CArrayRangeView(const T* data, size_t length)
: data_(data), length_(length) {}
const T* begin() const { return data_; }
const T* end() const { return data_ + length_; }
private:
const T* const data_;
const size_t length_;
};
class CdmImpl final : public Cdm, public WvCdmEventListener {
public:
CdmImpl(IEventListener* listener, IStorage* storage, bool privacy_mode,
const ReadOnlyStorage* owned_storage_object);
~CdmImpl() override;
// Cdm:
Status setServiceCertificate(ServiceRole role,
const std::string& certificate) override;
Status getServiceCertificateRequest(std::string* message) override;
Status parseAndLoadServiceCertificateResponse(
ServiceRole role, const std::string& response,
std::string* certificate) override;
Status getRobustnessLevel(RobustnessLevel* level) override;
Status getResourceRatingTier(uint32_t* tier) override;
Status getOemCryptoBuildInfo(std::string* build_info) override;
bool isProvisioned() override;
Status getProvisioningRequest(std::string* request) override;
Status handleProvisioningResponse(const std::string& response) override;
Status removeProvisioning() override;
Status listStoredLicenses(std::vector<std::string>* key_set_ids) override;
Status listUsageRecords(std::vector<std::string>* ksids) override;
Status deleteUsageRecord(const std::string& key_set_id) override;
Status deleteAllUsageRecords() override;
Status getStatusForHdcpVersion(HdcpVersion hdcp,
KeyStatus* key_status) override;
Status createSession(SessionType session_type,
std::string* session_id) override;
Status generateRequest(const std::string& session_id,
InitDataType init_data_type,
const std::string& init_data) override;
Status load(const std::string& session_id) override;
Status update(const std::string& session_id,
const std::string& response) override;
Status loadEmbeddedKeys(const std::string& session_id,
InitDataType init_data_type,
const std::string& init_data) override;
Status getExpiration(const std::string& session_id,
int64_t* expiration) override;
Status getKeyStatuses(const std::string& session_id,
KeyStatusMap* key_statuses) override;
Status getKeyAllowedUsages(const std::string& session_id,
const std::string& key_id,
KeyAllowedUsageFlags* usage_flags) override;
Status getKeyAllowedUsages(const std::string& key_id,
KeyAllowedUsageFlags* usage_flags) override;
Status setAppParameter(const std::string& key,
const std::string& value) override;
Status getAppParameter(const std::string& key, std::string* result) override;
Status removeAppParameter(const std::string& key) override;
Status clearAppParameters() override;
Status close(const std::string& session_id) override;
Status remove(const std::string& session_id) override;
Status forceRemove(const std::string& session_id) override;
Status decrypt(const DecryptionBatch& batch) override;
Status decrypt(const std::string& session_id,
const DecryptionBatch& batch) override;
Status genericEncrypt(const std::string& session_id,
const std::string& in_buffer, const std::string& key_id,
const std::string& iv,
GenericEncryptionAlgorithmType algorithm,
std::string* out_buffer) override;
Status genericDecrypt(const std::string& session_id,
const std::string& in_buffer, const std::string& key_id,
const std::string& iv,
GenericEncryptionAlgorithmType algorithm,
std::string* out_buffer) override;
Status genericSign(const std::string& session_id, const std::string& message,
const std::string& key_id,
GenericSigningAlgorithmType algorithm,
std::string* signature) override;
Status genericVerify(const std::string& session_id,
const std::string& message, const std::string& key_id,
GenericSigningAlgorithmType algorithm,
const std::string& signature) override;
Status setVideoResolution(const std::string& session_id, uint32_t width,
uint32_t height) override;
Status getMetrics(std::string* serialized_metrics) override;
// ITimerClient:
void onTimerExpired(void* context) override;
// WvCdmEventListener:
void OnSessionRenewalNeeded(const CdmSessionId& session_id) override;
void OnSessionKeysChange(const CdmSessionId& session_id,
const CdmKeyStatusMap& keys_status,
bool /* has_new_usable_key */) override;
void OnExpirationUpdate(const CdmSessionId& session_id,
int64_t new_expiry_time_seconds) override;
private:
KeyAllowedUsageFlags KeyAllowedFlags(const CdmKeyAllowedUsage& usages);
CdmEncryptionAlgorithm ConvertEncryptionAlgorithm(
GenericEncryptionAlgorithmType algorithm);
CdmSigningAlgorithm ConvertSigningAlgorithm(
GenericSigningAlgorithmType algorithm);
Cdm::Status ConvertHdcpLevel(const std::string& query_value,
Cdm::HdcpVersion* result);
IEventListener* listener_;
bool policy_timer_enabled_;
PropertySet property_set_;
std::string provisioning_service_certificate_;
FileSystem file_system_;
std::unique_ptr<CdmEngine> cdm_engine_;
CdmAppParameterMap app_parameters_;
struct SessionMetadata {
bool callable; // EME terminology: request generated or session loaded
SessionType type;
int64_t expiration;
KeyStatusMap key_statuses;
SessionMetadata() : callable(false), type((SessionType)-1), expiration(0) {}
};
std::map<std::string, SessionMetadata> sessions_;
// This field will be nullptr unless the create() function had to allocate
// memory for a read-only storage wrapper that needs to be cleaned up when
// this CDM instance is deleted. The storage should always be accessed via
// file_system_ and not via this parameter directly. However, it must be
// stored so it can be cleaned up later.
const std::unique_ptr<const ReadOnlyStorage> owned_storage_object_;
};
CdmImpl::CdmImpl(IEventListener* listener, IStorage* storage, bool privacy_mode,
const ReadOnlyStorage* owned_storage_object)
: listener_(listener),
policy_timer_enabled_(false),
provisioning_service_certificate_(),
file_system_("", storage),
cdm_engine_(CdmEngineFactory::CreateCdmEngine(&file_system_)),
owned_storage_object_(owned_storage_object) {
assert(nullptr != listener_);
property_set_.set_use_privacy_mode(privacy_mode);
}
CdmImpl::~CdmImpl() { host.timer->cancel(this); }
Cdm::Status CdmImpl::setServiceCertificate(ServiceRole role,
const std::string& certificate) {
if (certificate.empty()) {
LOGE("Service certificate string is empty.");
return kTypeError;
}
// Verify that the certificate is properly signed and well-formed.
CdmResponseType status = cdm_engine_->ValidateServiceCertificate(certificate);
if (status != NO_ERROR) {
LOGE("Invalid service certificate! Error code = %d",
static_cast<int>(status));
return kTypeError;
}
if (role == kLicensingService || role == kAllServices) {
property_set_.set_service_certificate(certificate);
// Update all open sessions with the new certificate.
for (const auto& session_pair : sessions_) {
cdm_engine_->SetSessionServiceCertificate(session_pair.first,
certificate);
}
}
if (role == kProvisioningService || role == kAllServices) {
provisioning_service_certificate_ = certificate;
}
return kSuccess;
}
Cdm::Status CdmImpl::getServiceCertificateRequest(std::string* message) {
if (!message) {
LOGE(
"Unable to return service certificate request - "
"string return parameter not supplied");
return kTypeError;
}
if (!ServiceCertificate::GetRequest(message)) {
LOGE("Unable to return service certificate request!");
message->clear();
return kTypeError;
}
return kSuccess;
}
Cdm::Status CdmImpl::parseAndLoadServiceCertificateResponse(
ServiceRole role, const std::string& response, std::string* certificate) {
std::string parsed_cert;
if (ServiceCertificate::ParseResponse(response, &parsed_cert) != NO_ERROR) {
LOGE("Failure parsing service certificate response!");
return kTypeError;
}
if (certificate) *certificate = parsed_cert;
return setServiceCertificate(role, parsed_cert);
}
Cdm::Status CdmImpl::getRobustnessLevel(RobustnessLevel* level) {
if (level == nullptr) {
LOGE("Missing level parameter to receive robustness level.");
return kTypeError;
}
std::string level_string;
const CdmResponseType result = cdm_engine_->QueryStatus(
kLevelDefault, QUERY_KEY_SECURITY_LEVEL, &level_string);
if (result == SYSTEM_INVALIDATED_ERROR) {
LOGE("System invalidated");
return kSystemStateLost;
} else if (result != NO_ERROR) {
LOGE("Unexpected error %d", static_cast<int>(result));
return kUnexpectedError;
}
if (level_string == QUERY_VALUE_SECURITY_LEVEL_L1) {
*level = kL1;
} else if (level_string == QUERY_VALUE_SECURITY_LEVEL_L2) {
*level = kL2;
} else if (level_string == QUERY_VALUE_SECURITY_LEVEL_L3) {
*level = kL3;
} else {
LOGE("Unknown robustness level: %s", level_string.c_str());
return kUnexpectedError;
}
return kSuccess;
}
Cdm::Status CdmImpl::getResourceRatingTier(uint32_t* tier) {
if (tier == nullptr) {
LOGE("Missing tier parameter to receive resource rating tier.");
return kTypeError;
}
std::string tier_string;
const CdmResponseType result = cdm_engine_->QueryStatus(
kLevelDefault, QUERY_KEY_RESOURCE_RATING_TIER, &tier_string);
if (result == SYSTEM_INVALIDATED_ERROR) {
LOGE("System invalidated");
return kSystemStateLost;
} else if (result != NO_ERROR) {
LOGE("Unexpected error %d", static_cast<int>(result));
return kUnexpectedError;
}
const uint32_t parsed_tier = static_cast<uint32_t>(std::stoul(tier_string));
if (parsed_tier <= 0) {
LOGE("Invalid resource rating tier %u", parsed_tier);
return kUnexpectedError;
}
*tier = parsed_tier;
return kSuccess;
}
Cdm::Status CdmImpl::getOemCryptoBuildInfo(std::string* build_info) {
if (build_info == nullptr) {
LOGE("Missing build_info parameter to receive build info.");
return kTypeError;
}
CdmResponseType result = cdm_engine_->QueryStatus(
kLevelDefault, QUERY_KEY_OEMCRYPTO_BUILD_INFORMATION, build_info);
if (result == SYSTEM_INVALIDATED_ERROR) {
LOGE("System invalidated");
return kSystemStateLost;
} else if (result != NO_ERROR) {
LOGE("Unexpected error %d", static_cast<int>(result));
return kUnexpectedError;
}
return kSuccess;
}
bool CdmImpl::isProvisioned() {
return cdm_engine_->IsProvisioned(kSecurityLevelL1);
}
Cdm::Status CdmImpl::getProvisioningRequest(std::string* request) {
std::string empty_authority;
std::string ignored_base_url;
CdmResponseType result = cdm_engine_->GetProvisioningRequest(
kCertificateWidevine, empty_authority, provisioning_service_certificate_,
kLevelDefault, request, &ignored_base_url);
if (result == CERT_PROVISIONING_NONCE_GENERATION_ERROR) {
LOGE("Nonce quota exceeded");
return kResourceContention;
} else if (result == SYSTEM_INVALIDATED_ERROR) {
LOGE("System invalidated");
return kSystemStateLost;
} else if (result != NO_ERROR) {
LOGE("Unexpected error %d", static_cast<int>(result));
return kUnexpectedError;
}
return kSuccess;
}
Cdm::Status CdmImpl::handleProvisioningResponse(const std::string& response) {
std::string ignored_cert;
std::string ignored_wrapped_key;
CdmResponseType result = cdm_engine_->HandleProvisioningResponse(
response, kLevelDefault, &ignored_cert, &ignored_wrapped_key);
if (result == SYSTEM_INVALIDATED_ERROR) {
LOGE("System invalidated");
return kSystemStateLost;
} else if (result != NO_ERROR) {
LOGE("Unexpected error %d", static_cast<int>(result));
return kUnexpectedError;
}
return kSuccess;
}
Cdm::Status CdmImpl::removeProvisioning() {
CdmResponseType result = cdm_engine_->Unprovision(kSecurityLevelL1);
if (result == SYSTEM_INVALIDATED_ERROR) {
LOGE("System invalidated");
return kSystemStateLost;
} else if (result != NO_ERROR) {
LOGE("Unexpected error %d", static_cast<int>(result));
return kUnexpectedError;
}
return kSuccess;
}
Cdm::Status CdmImpl::listStoredLicenses(std::vector<std::string>* key_set_ids) {
if (key_set_ids == nullptr) {
LOGE("Missing vector parameter to receive key_set_ids.");
return kTypeError;
}
CdmResponseType result =
cdm_engine_->ListStoredLicenses(kSecurityLevelL1, key_set_ids);
if (result == SYSTEM_INVALIDATED_ERROR) {
LOGE("System invalidated");
return kSystemStateLost;
} else if (result != NO_ERROR) {
LOGE("Unexpected error %d", static_cast<int>(result));
return kUnexpectedError;
}
return kSuccess;
}
Cdm::Status CdmImpl::listUsageRecords(std::vector<std::string>* ksids) {
if (ksids == nullptr) {
LOGE("Missing vector parameter to receive KSIDs.");
return kTypeError;
}
CdmResponseType result = cdm_engine_->ListUsageIds(
property_set_.app_id(), kSecurityLevelL1, ksids, nullptr);
if (result == SYSTEM_INVALIDATED_ERROR) {
LOGE("System invalidated");
return kSystemStateLost;
} else if (result != NO_ERROR) {
LOGE("Unexpected error %d", static_cast<int>(result));
return kUnexpectedError;
}
return kSuccess;
}
Cdm::Status CdmImpl::deleteUsageRecord(const std::string& key_set_id) {
CdmResponseType result = cdm_engine_->DeleteUsageRecord(
property_set_.app_id(), kSecurityLevelL1, key_set_id);
if (result == SYSTEM_INVALIDATED_ERROR) {
LOGE("System invalidated");
return kSystemStateLost;
} else if (result != NO_ERROR) {
LOGE("Unexpected error %d", static_cast<int>(result));
return kUnexpectedError;
}
return kSuccess;
}
Cdm::Status CdmImpl::deleteAllUsageRecords() {
CdmResponseType result =
cdm_engine_->RemoveAllUsageInfo(property_set_.app_id(), kSecurityLevelL1);
if (result == SYSTEM_INVALIDATED_ERROR) {
LOGE("System invalidated");
return kSystemStateLost;
} else if (result != NO_ERROR) {
LOGE("Unexpected error %d", static_cast<int>(result));
return kUnexpectedError;
}
return kSuccess;
}
Cdm::Status CdmImpl::getStatusForHdcpVersion(Cdm::HdcpVersion hdcp,
Cdm::KeyStatus* key_status) {
std::string query_value;
if (cdm_engine_->QueryStatus(kLevelDefault, QUERY_KEY_MAX_HDCP_LEVEL,
&query_value) != NO_ERROR) {
return kUnexpectedError;
}
if (query_value == QUERY_VALUE_HDCP_NONE ||
query_value == QUERY_VALUE_HDCP_LEVEL_UNKNOWN) {
*key_status = Cdm::kOutputRestricted;
} else if (query_value == QUERY_VALUE_HDCP_NO_DIGITAL_OUTPUT) {
*key_status = Cdm::kUsable;
} else {
Cdm::HdcpVersion max_hdcp;
if (ConvertHdcpLevel(query_value, &max_hdcp) != kSuccess) {
return kUnexpectedError;
}
*key_status = (hdcp <= max_hdcp ? Cdm::kUsable : Cdm::kOutputRestricted);
}
return kSuccess;
}
Cdm::Status CdmImpl::createSession(SessionType session_type,
std::string* session_id) {
if (session_id == nullptr) {
LOGE("Missing session ID pointer.");
return kTypeError;
}
// Important! The caller may pass a pre-filled string, which must be cleared
// before being given to CdmEngine.
session_id->clear();
switch (session_type) {
case kTemporary:
case kPersistentLicense:
case kPersistentUsageRecord:
break;
default:
LOGE("Unsupported session type: %d", static_cast<int>(session_type));
return kNotSupported;
}
CdmResponseType result = cdm_engine_->OpenSession(
"com.widevine.alpha", &property_set_, this, session_id);
switch (result) {
case NO_ERROR:
sessions_[*session_id].type = session_type;
return kSuccess;
case NEED_PROVISIONING:
// The Session ID may have been set by the CDM Engine despite this
// failure. We clear the ID here to ensure that we don't communicate a
// misleading ID to the application.
session_id->clear();
return kNeedsDeviceCertificate;
case SYSTEM_INVALIDATED_ERROR:
LOGE("System invalidated");
return kSystemStateLost;
default:
LOGE("Unexpected error %d", static_cast<int>(result));
return kUnexpectedError;
}
}
Cdm::Status CdmImpl::generateRequest(const std::string& session_id,
InitDataType init_data_type,
const std::string& init_data) {
if (!cdm_engine_->IsOpenSession(session_id)) {
LOGE("No such session: %s", session_id.c_str());
return kSessionNotFound;
}
if (sessions_[session_id].callable) {
LOGE("Request already generated: %s", session_id.c_str());
return kInvalidState;
}
const SessionType session_type = sessions_[session_id].type;
CdmLicenseType license_type;
switch (session_type) {
case kTemporary:
license_type = kLicenseTypeTemporary;
break;
case kPersistentLicense:
license_type = kLicenseTypeOffline;
break;
case kPersistentUsageRecord:
license_type = kLicenseTypeStreaming;
break;
default:
LOGE("Unexpected session type: %d", static_cast<int>(session_type));
return kUnexpectedError;
}
std::string init_data_type_name;
switch (init_data_type) {
case kCenc:
init_data_type_name = CENC_INIT_DATA_FORMAT;
break;
case kKeyIds:
LOGE("Key IDs init data type is not supported.");
return kNotSupported;
case kWebM:
init_data_type_name = WEBM_INIT_DATA_FORMAT;
break;
case kHls:
init_data_type_name = HLS_INIT_DATA_FORMAT;
break;
default:
LOGE("Invalid init data type: %d", static_cast<int>(init_data_type));
return kTypeError;
}
if (init_data.empty()) {
LOGE("Empty init data is not valid.");
return kTypeError;
}
std::string oec_version;
if (cdm_engine_->QueryStatus(wvcdm::kLevelDefault,
QUERY_KEY_OEMCRYPTO_API_VERSION,
&oec_version) != NO_ERROR) {
return kUnexpectedError;
}
InitializationData init_data_obj(init_data_type_name, init_data, oec_version);
if (init_data_obj.IsEmpty()) {
// Note that InitializationData's idea of "empty" includes "failed to find
// and parse a Widevine PSSH". This should not happen for WebM init data,
// which requires no parsing.
LOGE("Failed to parse init data, may not contain a Widevine PSSH.");
return kNotSupported;
}
CdmKeyRequest key_request;
const CdmResponseType result = cdm_engine_->GenerateKeyRequest(
session_id, session_id, init_data_obj, license_type, app_parameters_,
&key_request);
if (result == SYSTEM_INVALIDATED_ERROR) {
LOGE("System invalidated");
return kSystemStateLost;
} else if (result == SESSION_LOST_STATE_ERROR) {
LOGE("Session invalidated");
return kSessionStateLost;
} else if (result == LICENSE_REQUEST_NONCE_GENERATION_ERROR) {
LOGE("Nonce quota exceeded");
return kResourceContention;
} else if (result == NEED_PROVISIONING) {
LOGE("Device not provisioned");
return kNeedsDeviceCertificate;
} else if (result == PRIVACY_MODE_ERROR_1 || result == PRIVACY_MODE_ERROR_2 ||
result == PRIVACY_MODE_ERROR_3) {
LOGE("No licensing service certificate installed");
return kNeedsServiceCertificate;
} else if (result != KEY_MESSAGE) {
LOGE("Unexpected error %d", static_cast<int>(result));
return kUnexpectedError;
}
sessions_[session_id].callable = true;
assert(key_request.type == kKeyRequestTypeInitial);
LOGI("A license request has been generated");
listener_->onMessage(session_id, kLicenseRequest, key_request.message);
return kSuccess;
}
Cdm::Status CdmImpl::load(const std::string& session_id) {
if (session_id.empty()) {
LOGE("Empty session ID.");
return kTypeError;
}
if (cdm_engine_->IsOpenSession(session_id)) {
LOGE("Session ID already loaded.");
return kQuotaExceeded;
}
CdmResponseType result = cdm_engine_->OpenSession(
"com.widevine.alpha", &property_set_, session_id, this);
switch (result) {
case NO_ERROR:
break;
case SYSTEM_INVALIDATED_ERROR:
LOGE("System invalidated");
return kSystemStateLost;
case NEED_PROVISIONING:
return kNeedsDeviceCertificate;
default:
LOGE("Unexpected error %d", static_cast<int>(result));
return kUnexpectedError;
}
DeviceFiles f(&file_system_);
if (!f.Init(kSecurityLevelUnknown)) {
LOGE("Unexpected error, failed to init DeviceFiles");
return kUnexpectedError;
}
if (!f.LicenseExists(session_id)) {
// This might be a usage record session which needs to be loaded.
CdmKeyMessage ignored_release_message;
result =
cdm_engine_->LoadUsageSession(session_id, &ignored_release_message);
if (result != KEY_MESSAGE) {
cdm_engine_->CloseSession(session_id);
if (result == SYSTEM_INVALIDATED_ERROR) {
LOGE("System invalidated");
return kSystemStateLost;
} else if (result == LOAD_USAGE_INFO_MISSING) {
LOGE("Unable to load license: %s", session_id.c_str());
return kSessionNotFound;
} else {
LOGE("Unexpected error %d", static_cast<int>(result));
return kUnexpectedError;
}
}
sessions_[session_id].type = kPersistentUsageRecord;
sessions_[session_id].callable = true;
return kSuccess;
}
result = cdm_engine_->RestoreKey(session_id, session_id);
if (result == GET_RELEASED_LICENSE_ERROR) {
// This was partially removed already.
// The EME spec states that we should be able to load it, but not use it.
} else if (result == SYSTEM_INVALIDATED_ERROR) {
LOGE("System invalidated");
return kSystemStateLost;
} else if (result != KEY_ADDED) {
LOGE("Unexpected error %d", static_cast<int>(result));
return kUnexpectedError;
}
if (!policy_timer_enabled_) {
policy_timer_enabled_ = true;
host.timer->setTimeout(kPolicyTimerDurationMilliseconds, this,
kPolicyTimerContext);
}
sessions_[session_id].type = kPersistentLicense;
sessions_[session_id].callable = true;
return kSuccess;
}
Cdm::Status CdmImpl::update(const std::string& session_id,
const std::string& response) {
if (!cdm_engine_->IsOpenSession(session_id)) {
LOGE("No such session: %s", session_id.c_str());
return kSessionNotFound;
}
if (!sessions_[session_id].callable) {
LOGE("Request not yet generated: %s", session_id.c_str());
return kInvalidState;
}
if (response.empty()) {
LOGE("Empty response.");
return kTypeError;
}
// NOTE: If the CdmSession object recognizes that this is not the first
// AddKey(), it will internally delegate to RenewKey().
CdmKeySetId key_set_id = session_id;
CdmLicenseType license_type = {}; // Required for AddKey. Unused otherwise.
const CdmResponseType result =
cdm_engine_->AddKey(session_id, response, &license_type, &key_set_id);
// result should only be NEED_KEY after server certificate provisioning, which
// should no longer happen in this version of the CDM.
assert(result != NEED_KEY);
if (result == SYSTEM_INVALIDATED_ERROR) {
LOGE("System invalidated");
return kSystemStateLost;
} else if (result == SESSION_LOST_STATE_ERROR) {
LOGE("Session invalidated");
return kSessionStateLost;
} else if (result == OFFLINE_LICENSE_PROHIBITED) {
LOGE("A temporary session cannot be used for a persistent license.");
return kRangeError;
} else if (result == STORAGE_PROHIBITED) {
LOGE("A temporary session cannot be used for a persistent usage records.");
return kRangeError;
} else if (result == NEED_PROVISIONING) {
LOGE("The device needs to reprovision.");
return kNeedsDeviceCertificate;
} else if (result != KEY_ADDED) {
LOGE("Unexpected error %d", static_cast<int>(result));
return kUnexpectedError;
}
if (!policy_timer_enabled_) {
policy_timer_enabled_ = true;
host.timer->setTimeout(kPolicyTimerDurationMilliseconds, this,
kPolicyTimerContext);
}
if (cdm_engine_->IsReleaseSession(session_id)) {
sessions_.erase(session_id);
cdm_engine_->CloseSession(session_id);
listener_->onRemoveComplete(session_id);
}
return kSuccess;
}
Cdm::Status CdmImpl::loadEmbeddedKeys(const std::string& session_id,
InitDataType init_data_type,
const std::string& init_data) {
if (!cdm_engine_->IsOpenSession(session_id)) {
LOGE("Session %s does not exist.", session_id.c_str());
return kSessionNotFound;
}
if (init_data_type != kCenc) {
LOGE("Invalid init data type: %d", static_cast<int>(init_data_type));
return kTypeError;
}
std::string oec_version;
if (cdm_engine_->QueryStatus(wvcdm::kLevelDefault,
QUERY_KEY_OEMCRYPTO_API_VERSION,
&oec_version) != NO_ERROR) {
return kUnexpectedError;
}
InitializationData init_data_obj(CENC_INIT_DATA_FORMAT, init_data,
oec_version);
if (init_data_obj.IsEmpty()) {
// Note that InitializationData's idea of "empty" includes "failed to find
// and parse a Widevine PSSH". This should not happen for WebM init data,
// which requires no parsing.
LOGE("Failed to parse init data, may not contain a Widevine PSSH.");
return kNotSupported;
}
CdmKeyRequest key_request;
const CdmResponseType result = cdm_engine_->GenerateKeyRequest(
session_id, session_id, init_data_obj, kLicenseTypeEmbeddedKeyData,
app_parameters_, &key_request);
if (result == SYSTEM_INVALIDATED_ERROR) {
LOGE("System invalidated");
return kSystemStateLost;
} else if (result == SESSION_LOST_STATE_ERROR) {
LOGE("Session invalidated");
return kSessionStateLost;
} else if (result == LICENSE_REQUEST_NONCE_GENERATION_ERROR) {
LOGE("Nonce quota exceeded");
return kResourceContention;
} else if (result != KEY_ADDED) {
LOGE("Unexpected Failure: GenerateKeyRequest() returned %d",
static_cast<int>(result));
return kUnexpectedError;
}
return kSuccess;
}
Cdm::Status CdmImpl::getExpiration(const std::string& session_id,
int64_t* expiration) {
if (!cdm_engine_->IsOpenSession(session_id)) {
LOGE("No such session: %s", session_id.c_str());
return kSessionNotFound;
}
if (nullptr == expiration) {
LOGE("Missing pointer to expiration result.");
return kTypeError;
}
*expiration = sessions_[session_id].expiration;
return kSuccess;
}
Cdm::Status CdmImpl::getKeyStatuses(const std::string& session_id,
KeyStatusMap* key_statuses) {
if (!cdm_engine_->IsOpenSession(session_id)) {
LOGE("No such session: %s", session_id.c_str());
return kSessionNotFound;
}
if (nullptr == key_statuses) {
LOGE("Missing pointer to KeyStatusMap result.");
return kTypeError;
}
*key_statuses = sessions_[session_id].key_statuses;
return kSuccess;
}
Cdm::Status CdmImpl::getKeyAllowedUsages(const std::string& session_id,
const std::string& key_id,
KeyAllowedUsageFlags* usage_flags) {
if (!cdm_engine_->IsOpenSession(session_id)) {
LOGE("No such session: %s", session_id.c_str());
return kSessionNotFound;
}
if (nullptr == usage_flags) {
LOGE("Missing pointer to KeyAllowedUsageFlags result.");
return kTypeError;
}
CdmKeyAllowedUsage usage_for_key;
CdmResponseType result =
cdm_engine_->QueryKeyAllowedUsage(session_id, key_id, &usage_for_key);
if (result != NO_ERROR) {
// TODO(b/114435278): There are multiple KEY_NOT_FOUND_* errors that should
// probably all turn into kNoKey. Here, and below, and everywhere.
if (result == KEY_NOT_FOUND_1) {
return kNoKey;
} else if (result == SYSTEM_INVALIDATED_ERROR) {
LOGE("System invalidated");
return kSystemStateLost;
} else if (result == SESSION_LOST_STATE_ERROR) {
LOGE("Session invalidated");
return kSessionStateLost;
} else {
LOGE("Unexpected error %d", static_cast<int>(result));
return kUnexpectedError;
}
}
*usage_flags = KeyAllowedFlags(usage_for_key);
return kSuccess;
}
Cdm::Status CdmImpl::getKeyAllowedUsages(const std::string& key_id,
KeyAllowedUsageFlags* usage_flags) {
if (nullptr == usage_flags) {
LOGE("Missing pointer to KeyAllowedUsageFlags result.");
return kTypeError;
}
CdmKeyAllowedUsage usage_for_key;
const CdmResponseType result =
cdm_engine_->QueryKeyAllowedUsage(key_id, &usage_for_key);
if (result != NO_ERROR) {
if (result == KEY_NOT_FOUND_1 || result == KEY_NOT_FOUND_2) {
return kNoKey;
} else if (result == SYSTEM_INVALIDATED_ERROR) {
LOGE("System invalidated");
return kSystemStateLost;
} else if (result == SESSION_LOST_STATE_ERROR) {
LOGE("Session invalidated");
return kSessionStateLost;
} else if (result == KEY_CONFLICT_1) {
return kTypeError;
} else {
LOGE("Unexpected error %d", static_cast<int>(result));
return kUnexpectedError;
}
}
*usage_flags = KeyAllowedFlags(usage_for_key);
return kSuccess;
}
Cdm::Status CdmImpl::setAppParameter(const std::string& key,
const std::string& value) {
if (key.empty()) {
return kTypeError;
}
app_parameters_[key] = value;
return kSuccess;
}
Cdm::Status CdmImpl::getAppParameter(const std::string& key,
std::string* result) {
if (nullptr == result || key.empty() ||
app_parameters_.find(key) == app_parameters_.end()) {
return kTypeError;
}
*result = app_parameters_[key];
return kSuccess;
}
Cdm::Status CdmImpl::removeAppParameter(const std::string& key) {
if (key.empty()) {
return kTypeError;
}
CdmAppParameterMap::iterator it = app_parameters_.find(key);
if (it == app_parameters_.end()) {
return kTypeError;
}
app_parameters_.erase(it);
return kSuccess;
}
Cdm::Status CdmImpl::clearAppParameters() {
app_parameters_.clear();
return kSuccess;
}
Cdm::Status CdmImpl::close(const std::string& session_id) {
if (!cdm_engine_->IsOpenSession(session_id)) {
LOGE("No such session: %s", session_id.c_str());
return kSessionNotFound;
}
const CdmResponseType result = cdm_engine_->CloseSession(session_id);
if (result == SYSTEM_INVALIDATED_ERROR) {
LOGE("System invalidated");
return kSystemStateLost;
} else if (result != NO_ERROR) {
LOGE("Unexpected error %d", static_cast<int>(result));
return kUnexpectedError;
}
sessions_.erase(session_id);
return kSuccess;
}
Cdm::Status CdmImpl::remove(const std::string& session_id) {
if (!cdm_engine_->IsOpenSession(session_id)) {
LOGE("No such session: %s", session_id.c_str());
return kSessionNotFound;
}
if (!sessions_[session_id].callable) {
LOGE("Request not yet generated: %s", session_id.c_str());
return kInvalidState;
}
if (sessions_[session_id].type == kTemporary) {
LOGE("Not a persistent session: %s", session_id.c_str());
return kRangeError;
}
InitializationData empty_initialization_data;
CdmKeyRequest key_request;
// Mark all keys as released ahead of generating the release request.
// When released, cdm_engine_ will mark all keys as expired, which we will
// ignore in this interface.
KeyStatusMap& map = sessions_[session_id].key_statuses;
for (KeyStatusMap::iterator it = map.begin(); it != map.end(); ++it) {
it->second = kReleased;
}
const CdmResponseType result = cdm_engine_->GenerateKeyRequest(
session_id, session_id, empty_initialization_data, kLicenseTypeRelease,
app_parameters_, &key_request);
if (result == SYSTEM_INVALIDATED_ERROR) {
LOGE("System invalidated");
return kSystemStateLost;
} else if (result == SESSION_LOST_STATE_ERROR) {
LOGE("Session invalidated");
return kSessionStateLost;
} else if (result == LICENSE_REQUEST_NONCE_GENERATION_ERROR) {
LOGE("Nonce quota exceeded");
return kResourceContention;
} else if (result != KEY_MESSAGE) {
LOGE("Unexpected error %d", static_cast<int>(result));
cdm_engine_->CloseSession(session_id);
return kUnexpectedError;
}
LOGI("A license release has been generated.");
MessageType message_type = kLicenseRelease;
listener_->onMessage(session_id, message_type, key_request.message);
return kSuccess;
}
Cdm::Status CdmImpl::forceRemove(const std::string& session_id) {
if (!cdm_engine_->IsOpenSession(session_id)) {
LOGE("No such session: %s", session_id.c_str());
return kSessionNotFound;
}
if (!sessions_[session_id].callable) {
LOGE("Request not yet generated: %s", session_id.c_str());
return kInvalidState;
}
if (sessions_[session_id].type == kTemporary) {
LOGE("Not a persistent session: %s", session_id.c_str());
return kRangeError;
}
const CdmResponseType result = cdm_engine_->RemoveLicense(session_id);
if (result == SYSTEM_INVALIDATED_ERROR) {
LOGE("System invalidated");
return kSystemStateLost;
} else if (result == SESSION_LOST_STATE_ERROR) {
LOGE("Session invalidated");
return kSessionStateLost;
} else if (result != NO_ERROR) {
LOGE("Unexpected error %d", static_cast<int>(result));
return kUnexpectedError;
}
sessions_.erase(session_id);
cdm_engine_->CloseSession(session_id);
listener_->onRemoveComplete(session_id);
LOGI("A session has been forcibly deleted.");
return kSuccess;
}
Cdm::Status CdmImpl::decrypt(const DecryptionBatch& batch) {
std::string empty_session_id;
return decrypt(empty_session_id, batch);
}
Cdm::Status CdmImpl::decrypt(const std::string& session_id,
const DecryptionBatch& batch) {
if (batch.encryption_scheme == kAesCtr &&
(batch.pattern.encrypted_blocks != 0 ||
batch.pattern.clear_blocks != 0)) {
LOGE("The 'cens' schema is not supported.");
return kNotSupported;
}
if (PropertiesCE::GetSecureOutputType() == kNoSecureOutput &&
batch.is_secure) {
LOGE("The CDM is configured without secure output support.");
return kNotSupported;
}
const CArrayRangeView<Sample> samples(batch.samples, batch.samples_length);
for (const Sample& sample : samples) {
if (sample.input.data_length >
(sample.output.data_length - sample.output.data_offset)) {
LOGE("The output buffer is too small to contain the input buffer.");
return kTypeError;
}
const CArrayRangeView<Subsample> subsamples(sample.input.subsamples,
sample.input.subsamples_length);
const bool is_encrypted =
(batch.encryption_scheme != kClear) &&
std::any_of(std::begin(subsamples), std::end(subsamples),
[](const Subsample& subsample) -> bool {
return subsample.protected_bytes > 0;
});
if (is_encrypted && sample.input.iv_length != 16) {
LOGE("The IV must be 16 bytes long.");
return kTypeError;
}
}
// With the input validated, we may copy it into a CdmDecryptionParametersV16
CdmDecryptionParametersV16 parameters;
parameters.key_id.assign(batch.key_id, batch.key_id + batch.key_id_length);
parameters.is_secure = batch.is_secure;
if (batch.encryption_scheme == kAesCtr) {
parameters.cipher_mode = kCipherModeCtr;
} else if (batch.encryption_scheme == kAesCbc) {
parameters.cipher_mode = kCipherModeCbc;
}
parameters.is_video = batch.is_video;
parameters.pattern.encrypt_blocks = batch.pattern.encrypted_blocks;
parameters.pattern.skip_blocks = batch.pattern.clear_blocks;
parameters.samples.reserve(batch.samples_length);
std::transform(
std::begin(samples), std::end(samples),
std::back_inserter(parameters.samples),
[](const Sample& sample) -> CdmDecryptionSample {
CdmDecryptionSample cdm_sample;
cdm_sample.encrypt_buffer = sample.input.data;
cdm_sample.encrypt_buffer_length = sample.input.data_length;
cdm_sample.decrypt_buffer = sample.output.data;
cdm_sample.decrypt_buffer_size = sample.output.data_length;
cdm_sample.decrypt_buffer_offset = sample.output.data_offset;
cdm_sample.iv.assign(sample.input.iv,
sample.input.iv + sample.input.iv_length);
const CArrayRangeView<Subsample> subsamples(
sample.input.subsamples, sample.input.subsamples_length);
cdm_sample.subsamples.reserve(sample.input.subsamples_length);
std::transform(
std::begin(subsamples), std::end(subsamples),
std::back_inserter(cdm_sample.subsamples),
[](const Subsample& subsample) -> CdmDecryptionSubsample {
return CdmDecryptionSubsample(subsample.clear_bytes,
subsample.protected_bytes);
});
return cdm_sample;
});
const CdmResponseType result =
cdm_engine_->DecryptV16(session_id, parameters);
if (result == NO_ERROR) {
return kSuccess;
}
if (result == SYSTEM_INVALIDATED_ERROR) {
LOGE("System invalidated");
return kSystemStateLost;
}
if (result == SESSION_LOST_STATE_ERROR) {
LOGE("Session invalidated");
return kSessionStateLost;
}
if (result == OUTPUT_TOO_LARGE_ERROR) {
LOGE("Output too large");
return kOutputTooLarge;
}
if (result == NEED_KEY || result == KEY_NOT_FOUND_3 ||
result == SESSION_NOT_FOUND_FOR_DECRYPT) {
LOGE("Key not available.");
return kNoKey;
}
if (result == INSUFFICIENT_OUTPUT_PROTECTION) {
LOGE("Key usage blocked due to HDCP or display resolution constraints.");
return kKeyUsageBlockedByPolicy;
}
LOGE("Decrypt error: %d", static_cast<int>(result));
return kDecryptError;
}
Cdm::Status CdmImpl::genericEncrypt(const std::string& session_id,
const std::string& in_buffer,
const std::string& key_id,
const std::string& iv,
GenericEncryptionAlgorithmType algorithm,
std::string* out_buffer) {
const CdmEncryptionAlgorithm cdm_algorithm =
ConvertEncryptionAlgorithm(algorithm);
if (cdm_algorithm == wvcdm::kEncryptionAlgorithmUnknown) {
LOGE("Unrecognized encryption algorithm: %d.",
static_cast<int>(cdm_algorithm));
return kNotSupported;
}
CdmResponseType result = cdm_engine_->GenericEncrypt(
session_id, in_buffer, key_id, iv, cdm_algorithm, out_buffer);
if (result == NO_ERROR) {
return kSuccess;
}
if (result == SYSTEM_INVALIDATED_ERROR) {
LOGE("System invalidated");
return kSystemStateLost;
}
if (result == SESSION_LOST_STATE_ERROR) {
LOGE("Session invalidated");
return kSessionStateLost;
}
if (result == OUTPUT_TOO_LARGE_ERROR) {
LOGE("Output too large");
return kOutputTooLarge;
}
if (result == SESSION_NOT_FOUND_13) {
LOGE("No such session: %s", session_id.c_str());
return kSessionNotFound;
}
if (result == KEY_NOT_FOUND_3 || result == NEED_KEY) {
LOGE("Key Error: %s", session_id.c_str());
return kNoKey;
}
LOGE("Unexpected error %d", static_cast<int>(result));
return kUnexpectedError;
}
Cdm::Status CdmImpl::genericDecrypt(const std::string& session_id,
const std::string& in_buffer,
const std::string& key_id,
const std::string& iv,
GenericEncryptionAlgorithmType algorithm,
std::string* out_buffer) {
const CdmEncryptionAlgorithm cdm_algorithm =
ConvertEncryptionAlgorithm(algorithm);
if (cdm_algorithm == wvcdm::kEncryptionAlgorithmUnknown) {
LOGE("Unrecognized encryption algorithm: %d.",
static_cast<int>(cdm_algorithm));
return kNotSupported;
}
const CdmResponseType result = cdm_engine_->GenericDecrypt(
session_id, in_buffer, key_id, iv, cdm_algorithm, out_buffer);
if (result == NO_ERROR) {
return kSuccess;
}
if (result == SYSTEM_INVALIDATED_ERROR) {
LOGE("System invalidated");
return kSystemStateLost;
}
if (result == SESSION_LOST_STATE_ERROR) {
LOGE("Session invalidated");
return kSessionStateLost;
}
if (result == OUTPUT_TOO_LARGE_ERROR) {
LOGE("Output too large");
return kOutputTooLarge;
}
if (result == SESSION_NOT_FOUND_14) {
LOGE("No such session: %s", session_id.c_str());
return kSessionNotFound;
}
if (result == KEY_NOT_FOUND_4 || result == NEED_KEY) {
LOGE("Key Error: %s", session_id.c_str());
return kNoKey;
}
LOGE("Unexpected error %d", static_cast<int>(result));
return kUnexpectedError;
}
Cdm::Status CdmImpl::genericSign(const std::string& session_id,
const std::string& message,
const std::string& key_id,
GenericSigningAlgorithmType algorithm,
std::string* signature) {
const CdmSigningAlgorithm cdm_algorithm = ConvertSigningAlgorithm(algorithm);
if (cdm_algorithm == wvcdm::kSigningAlgorithmUnknown) {
LOGE("Unrecognized signing algorithm: %d", static_cast<int>(cdm_algorithm));
return kNotSupported;
}
const CdmResponseType result = cdm_engine_->GenericSign(
session_id, message, key_id, cdm_algorithm, signature);
if (result == NO_ERROR) {
return kSuccess;
}
if (result == SYSTEM_INVALIDATED_ERROR) {
LOGE("System invalidated");
return kSystemStateLost;
}
if (result == SESSION_LOST_STATE_ERROR) {
LOGE("Session invalidated");
return kSessionStateLost;
}
if (result == SESSION_NOT_FOUND_15) {
LOGE("No such session: %s", session_id.c_str());
return kSessionNotFound;
}
if (result == KEY_NOT_FOUND_5 || result == NEED_KEY) {
LOGE("Key Error: %s", session_id.c_str());
return kNoKey;
}
LOGE("Unexpected error %d", static_cast<int>(result));
return kUnexpectedError;
}
Cdm::Status CdmImpl::genericVerify(const std::string& session_id,
const std::string& message,
const std::string& key_id,
GenericSigningAlgorithmType algorithm,
const std::string& signature) {
const CdmSigningAlgorithm cdm_algorithm = ConvertSigningAlgorithm(algorithm);
if (cdm_algorithm == wvcdm::kSigningAlgorithmUnknown) {
LOGE("Unrecognized signing algorithm: %d", static_cast<int>(cdm_algorithm));
return kNotSupported;
}
const CdmResponseType result = cdm_engine_->GenericVerify(
session_id, message, key_id, cdm_algorithm, signature);
if (result == NO_ERROR) {
return kSuccess;
}
if (result == SYSTEM_INVALIDATED_ERROR) {
LOGE("System invalidated");
return kSystemStateLost;
}
if (result == SESSION_LOST_STATE_ERROR) {
LOGE("Session invalidated");
return kSessionStateLost;
}
if (result == SESSION_NOT_FOUND_16) {
LOGE("No such session: %s", session_id.c_str());
return kSessionNotFound;
}
if (result == KEY_NOT_FOUND_6 || result == NEED_KEY) {
LOGE("Key Error: %s", session_id.c_str());
return kNoKey;
}
LOGE("Unexpected error %d", static_cast<int>(result));
return kUnexpectedError;
}
Cdm::Status CdmImpl::setVideoResolution(const std::string& session_id,
uint32_t width, uint32_t height) {
constexpr uint64_t kMaxUint32 = std::numeric_limits<uint32_t>::max();
// Verify that width * height will fit into a 32-bit quantity.
// This is done to be compatible with the video resolution in the
// license policy settings.
const uint64_t pixels =
static_cast<uint64_t>(width) * static_cast<uint64_t>(height);
if (pixels > kMaxUint32) return kRangeError;
if (cdm_engine_->NotifyResolution(session_id, width, height)) {
return kSuccess;
} else {
return kSessionNotFound;
}
}
Cdm::Status CdmImpl::getMetrics(std::string* serialized_metrics) {
if (serialized_metrics == nullptr) {
LOGE("Missing string parameter to receive serialized metrics.");
return kTypeError;
}
drm_metrics::WvCdmMetrics metrics;
if (!cdm_engine_->GetMetricsSnapshot(&metrics) ||
!metrics.SerializeToString(serialized_metrics)) {
return kUnexpectedError;
}
return kSuccess;
}
void CdmImpl::onTimerExpired(void* context) {
if (context == kPolicyTimerContext) {
if (policy_timer_enabled_) {
cdm_engine_->OnTimerEvent();
host.timer->setTimeout(kPolicyTimerDurationMilliseconds, this,
kPolicyTimerContext);
}
}
}
void CdmImpl::OnSessionRenewalNeeded(const CdmSessionId& session_id) {
CdmKeyRequest key_request;
const CdmResponseType result =
cdm_engine_->GenerateRenewalRequest(session_id, &key_request);
if (result == LICENSE_RENEWAL_NONCE_GENERATION_ERROR) {
// TODO(b/73606893): this error should be recoverable. Rather
// than just giving up here, we should reset the event timer for
// a second later and try again.
LOGE("Nonce quota exceeded");
return;
}
if (result != KEY_MESSAGE) {
LOGE("Unexpected error %d", static_cast<int>(result));
return;
}
LOGI("A license renewal has been generated.");
MessageType message_type = kLicenseRenewal;
listener_->onMessage(session_id, message_type, key_request.message);
}
void CdmImpl::OnSessionKeysChange(const CdmSessionId& session_id,
const CdmKeyStatusMap& keys_status,
bool has_new_usable_key) {
KeyStatusMap& map = sessions_[session_id].key_statuses;
CdmKeyStatusMap::const_iterator it;
for (it = keys_status.begin(); it != keys_status.end(); ++it) {
switch (it->second) {
case kKeyStatusUsable:
map[it->first] = kUsable;
continue;
case kKeyStatusExpired: {
KeyStatusMap::const_iterator it_old = map.find(it->first);
if (it_old != map.end() && it_old->second == kReleased) {
// This key has already been marked as "released".
// Ignore the internal "expired" status.
} else {
map[it->first] = kExpired;
}
continue;
}
case kKeyStatusOutputNotAllowed:
map[it->first] = kOutputRestricted;
continue;
case kKeyStatusUsableInFuture:
case kKeyStatusPending:
map[it->first] = kStatusPending;
continue;
case kKeyStatusKeyUnknown:
case kKeyStatusInternalError:
map[it->first] = kInternalError;
continue;
}
// If we get this far, the switch statement fell through, which is an error.
// We do this instead of having a default case in the switch so that the
// compiler will flag if someone fails to add a case for new enum members,
// while still handling the possibility of an invalid value sneaking into
// the map at runtime.
LOGE("Internal error: Invalid key status: %d",
static_cast<int>(it->second));
map[it->first] = kInternalError;
}
listener_->onKeyStatusesChange(session_id, has_new_usable_key);
}
void CdmImpl::OnExpirationUpdate(const CdmSessionId& session_id,
int64_t new_expiry_time_seconds) {
// "Never expires" in core is NEVER_EXPIRES. In the CDM API, it's -1.
if (new_expiry_time_seconds == NEVER_EXPIRES) {
sessions_[session_id].expiration = -1;
} else {
sessions_[session_id].expiration = new_expiry_time_seconds * 1000;
}
}
Cdm::KeyAllowedUsageFlags CdmImpl::KeyAllowedFlags(
const CdmKeyAllowedUsage& usages) {
KeyAllowedUsageFlags flags = kAllowNone;
flags |= (usages.decrypt_to_clear_buffer) ? kAllowDecryptToClearBuffer : 0;
flags |= (usages.decrypt_to_secure_buffer) ? kAllowDecryptToSecureBuffer : 0;
flags |= (usages.generic_encrypt) ? kAllowGenericEncrypt : 0;
flags |= (usages.generic_decrypt) ? kAllowGenericDecrypt : 0;
flags |= (usages.generic_sign) ? kAllowGenericSign : 0;
flags |= (usages.generic_verify) ? kAllowGenericSignatureVerify : 0;
return flags;
}
CdmEncryptionAlgorithm CdmImpl::ConvertEncryptionAlgorithm(
GenericEncryptionAlgorithmType algorithm) {
if (algorithm == Cdm::kEncryptionAlgorithmAesCbc128) {
return wvcdm::kEncryptionAlgorithmAesCbc128;
} else {
LOGW("Unknown encryption algorithm: %d", static_cast<int>(algorithm));
return wvcdm::kEncryptionAlgorithmUnknown;
}
}
CdmSigningAlgorithm CdmImpl::ConvertSigningAlgorithm(
GenericSigningAlgorithmType algorithm) {
if (algorithm == Cdm::kSigningAlgorithmHmacSha256) {
return wvcdm::kSigningAlgorithmHmacSha256;
} else {
LOGW("Unknown signing algorithm: %d", static_cast<int>(algorithm));
return wvcdm::kSigningAlgorithmUnknown;
}
}
Cdm::Status CdmImpl::ConvertHdcpLevel(const std::string& query_value,
Cdm::HdcpVersion* result) {
if (query_value == QUERY_VALUE_HDCP_V1) {
*result = kHdcp1_x;
} else if (query_value == QUERY_VALUE_HDCP_V2_0) {
*result = kHdcp2_0;
} else if (query_value == QUERY_VALUE_HDCP_V2_1) {
*result = kHdcp2_1;
} else if (query_value == QUERY_VALUE_HDCP_V2_2) {
*result = kHdcp2_2;
} else if (query_value == QUERY_VALUE_HDCP_V2_3) {
*result = kHdcp2_3;
} else {
return kUnexpectedError;
}
return kSuccess;
}
} // namespace
// static
Cdm::Status Cdm::initialize(SecureOutputType secure_output_type,
const ClientInfo& client_info, IStorage* storage,
IClock* clock, ITimer* timer, LogLevel verbosity) {
return initialize(secure_output_type, client_info, storage, clock, timer,
verbosity, kNoSandboxId);
}
// static
Cdm::Status Cdm::initialize(SecureOutputType secure_output_type,
const ClientInfo& client_info, IStorage* storage,
IClock* clock, ITimer* timer, LogLevel verbosity,
const std::string& sandbox_id) {
// Specify the maximum severity of message that will be output to
// the console. See core/include/log.h for the valid priority values.
g_cutoff = static_cast<LogPriority>(verbosity);
switch (secure_output_type) {
case kOpaqueHandle:
case kDirectRender:
case kNoSecureOutput:
break;
default:
LOGE("Invalid output type!");
return kTypeError;
}
if (client_info.product_name.empty() || client_info.company_name.empty() ||
client_info.model_name.empty()) {
LOGE("Client info requires product_name, company_name, model_name!");
return kTypeError;
}
if (!storage || !clock || !timer) {
LOGE("All interfaces are required!");
return kTypeError;
}
PropertiesCE::SetSecureOutputType(secure_output_type);
PropertiesCE::SetClientInfo(client_info);
if (sandbox_id != kNoSandboxId) PropertiesCE::SetSandboxId(sandbox_id);
Properties::Init();
host.storage = storage;
host.clock = clock;
host.timer = timer;
host.initialized = true;
return kSuccess;
}
// static
const char* Cdm::version() { return CDM_VERSION; }
// static
Cdm* Cdm::create(IEventListener* listener, IStorage* storage,
bool privacy_mode) {
return create(listener, storage, privacy_mode,
false /* storage_is_read_only */);
}
// static
Cdm* Cdm::create(IEventListener* listener, IStorage* storage, bool privacy_mode,
bool storage_is_read_only) {
if (!host.initialized) {
LOGE("Not initialized!");
return nullptr;
}
if (!listener) {
LOGE("No listener!");
return nullptr;
}
if (!storage) {
LOGE("No storage!");
return nullptr;
}
if (storage_is_read_only) {
ReadOnlyStorage* read_only_storage = new ReadOnlyStorage(storage);
return new CdmImpl(listener, read_only_storage, privacy_mode,
read_only_storage);
} else {
return new CdmImpl(listener, storage, privacy_mode, nullptr);
}
}
} // namespace widevine
// Missing symbols from core:
namespace wvcdm {
using namespace widevine;
int64_t Clock::GetCurrentTime() { return host.clock->now() / 1000; }
class FileImpl final : public File {
public:
FileImpl(Cdm::IStorage* storage, const std::string& path, int flags)
: storage_(storage),
name_(path),
read_only_(flags & FileSystem::kReadOnly),
truncate_(flags & FileSystem::kTruncate) {
assert(storage_);
}
ssize_t Read(char* buffer, size_t bytes) override {
if (!buffer) {
LOGW("File::Read: buffer is empty");
return -1;
}
std::string data;
if (!storage_->read(name_, &data)) {
return -1;
}
size_t to_copy = std::min(bytes, data.size());
memcpy(buffer, data.data(), to_copy);
return to_copy;
}
ssize_t Write(const char* buffer, size_t bytes) override {
if (!buffer) {
LOGW("File::Write: buffer is empty");
return -1;
}
if (read_only_) {
LOGE("File::Write: file is read-only.");
return -1;
}
if (!truncate_) {
LOGE("File::Write: files cannot be appended to.");
return -1;
}
std::string data(buffer, bytes);
if (!storage_->write(name_, data)) {
return -1;
}
return bytes;
}
private:
Cdm::IStorage* const storage_;
const std::string name_;
const bool read_only_;
const bool truncate_;
};
class FileSystem::Impl {
public:
Impl(widevine::Cdm::IStorage* storage) : storage_(storage) {
assert(storage);
}
widevine::Cdm::IStorage* const storage_;
};
FileSystem::FileSystem() : impl_(new Impl(host.storage)) {}
FileSystem::FileSystem(const std::string& origin, void* extra_data)
: impl_(new Impl(reinterpret_cast<widevine::Cdm::IStorage*>(extra_data))),
origin_(origin) {}
FileSystem::~FileSystem() {}
std::unique_ptr<File> FileSystem::Open(const std::string& file_path,
int flags) {
if (!(flags & kCreate) && !impl_->storage_->exists(file_path)) {
return nullptr;
}
return std::unique_ptr<File>(new FileImpl(impl_->storage_, file_path, flags));
}
bool FileSystem::Exists(const std::string& file_path) {
return !file_path.empty() && impl_->storage_->exists(file_path);
}
bool FileSystem::Remove(const std::string& file_path) {
return impl_->storage_->remove(file_path);
}
ssize_t FileSystem::FileSize(const std::string& file_path) {
return impl_->storage_->size(file_path);
}
bool FileSystem::List(const std::string&,
std::vector<std::string>* file_names) {
return file_names && impl_->storage_->list(file_names);
}
} // namespace wvcdm