// 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 #include // LLONG_MAX #include // memcpy #include #include #include #include // 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* 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 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* key_set_ids) override; Status listUsageRecords(std::vector* 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 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 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 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(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(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(result)); return kUnexpectedError; } const uint32_t parsed_tier = static_cast(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(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(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(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(result)); return kUnexpectedError; } return kSuccess; } Cdm::Status CdmImpl::listStoredLicenses(std::vector* 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(result)); return kUnexpectedError; } return kSuccess; } Cdm::Status CdmImpl::listUsageRecords(std::vector* 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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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 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 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 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(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(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(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(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(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(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(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(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(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::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(width) * static_cast(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(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(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(algorithm)); return wvcdm::kEncryptionAlgorithmUnknown; } } CdmSigningAlgorithm CdmImpl::ConvertSigningAlgorithm( GenericSigningAlgorithmType algorithm) { if (algorithm == Cdm::kSigningAlgorithmHmacSha256) { return wvcdm::kSigningAlgorithmHmacSha256; } else { LOGW("Unknown signing algorithm: %d", static_cast(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(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(extra_data))), origin_(origin) {} FileSystem::~FileSystem() {} std::unique_ptr FileSystem::Open(const std::string& file_path, int flags) { if (!(flags & kCreate) && !impl_->storage_->exists(file_path)) { return nullptr; } return std::unique_ptr(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* file_names) { return file_names && impl_->storage_->list(file_names); } } // namespace wvcdm