#include "widevine_cas_api.h" #include #include "cas_events.h" #include "cas_util.h" #include "license_protocol.pb.h" #include "log.h" #include "media_cas.pb.h" #include "string_conversions.h" #include "widevine_cas_session_map.h" constexpr char kBasePathPrefix[] = "/data/vendor/mediacas/IDM/widevine/"; constexpr char kCertFileBase[] = "cert.bin"; constexpr char kLicenseFileNameSuffix[] = ".lic"; namespace { bool ReadFileFromStorage(wvutil::FileSystem& file_system, const std::string& filename, std::string* file_data) { if (nullptr == file_data) { return false; } if (!file_system.Exists(filename)) { return false; } size_t filesize = file_system.FileSize(filename); if (0 == filesize) { return false; } file_data->resize(filesize); std::unique_ptr file = file_system.Open(filename, wvutil::FileSystem::kReadOnly); if (nullptr == file) { return false; } size_t bytes_read = file->Read(&(*file_data)[0], file_data->size()); if (bytes_read != filesize) { return false; } return true; } bool RemoveFile(wvutil::FileSystem& file_system, const std::string& filename) { if (!file_system.Exists(filename)) { return false; } if (!file_system.Remove(filename)) { return false; } return true; } bool StoreFile(wvutil::FileSystem& file_system, const std::string& filename, const std::string& file_data) { std::unique_ptr file(file_system.Open( filename, wvutil::FileSystem::kTruncate | wvutil::FileSystem::kCreate)); if (nullptr == file) { return false; } size_t bytes_written = file->Write(file_data.data(), file_data.size()); if (bytes_written != file_data.size()) { return false; } return true; } std::string GenerateLicenseFilename(const std::string& content_id, const std::string& provider_id) { std::string data(content_id + provider_id); std::string hash; hash.resize(SHA256_DIGEST_LENGTH); const unsigned char* input = reinterpret_cast(data.data()); unsigned char* output = reinterpret_cast(&hash[0]); SHA256(input, data.size(), output); return std::string(std::string(kBasePathPrefix) + wvutil::b2a_hex(hash) + std::string(kLicenseFileNameSuffix)); } } // namespace namespace wvcas { class MediaContext : public CasMediaId { public: MediaContext() : CasMediaId() {} ~MediaContext() override {} MediaContext(const MediaContext&) = delete; MediaContext& operator=(const MediaContext&) = delete; const std::string content_id() override { return pssh_.content_id(); } const std::string provider_id() override { return pssh_.provider(); } CasStatus initialize(const std::string& init_data) override { if (!pssh_.ParseFromString(init_data)) { return CasStatus(CasStatusCode::kInvalidParameter, "invalid init_data"); } return CasStatusCode::kNoError; } private: video_widevine::WidevinePsshData pssh_; }; std::unique_ptr CasMediaId::create() { std::unique_ptr ctx = make_unique(); return std::move(ctx); } std::shared_ptr WidevineCas::getCryptoSession() { return std::make_shared(); } std::unique_ptr WidevineCas::getCasLicense() { return make_unique(); } std::unique_ptr WidevineCas::getFileSystem() { return make_unique(); } std::shared_ptr WidevineCas::newCasSession() { return std::make_shared(); } void WidevineCas::OnTimerEvent() { std::unique_lock locker(lock_); if (cas_license_.get() != nullptr) { cas_license_->OnTimerEvent(); // Delete expired license after firing expired event in policy_engine if (cas_license_->IsExpired() && (media_id_.get() != nullptr)) { std::string filename = GenerateLicenseFilename(media_id_->content_id(), media_id_->provider_id()); if (!file_system_->Exists(filename)) { LOGI("No expired license file stored in disk"); } else { if (RemoveFile(*file_system_, filename)) { LOGI("Remove expired license file from disk successfully."); } } } } } CasStatus WidevineCas::initialize(CasEventListener* event_listener) { std::unique_lock locker(lock_); crypto_session_ = getCryptoSession(); // For session name generation. srand(time(nullptr)); // Setup an oemcrypto session. CasStatus status = crypto_session_->initialize(); if (!status.ok()) { LOGE("WidevineCas initialization failed: %d", status.status_code()); return status; } file_system_ = getFileSystem(); cas_license_ = getCasLicense(); status = cas_license_->initialize(crypto_session_, event_listener); if (!status.ok()) { LOGE("WidevineCas initialization failed: %d", status.status_code()); return status; } std::string cert_filename_path(std::string(kBasePathPrefix) + std::string(kCertFileBase)); // Try to read a certificate if one exists. If any error occurs, just ignore // it and let new cert file overwrite the existing file. std::string cert_file; if (ReadFileFromStorage(*file_system_, cert_filename_path, &cert_file)) { LOGI("read cert.bin successfully"); if (!HandleStoredDrmCert(cert_file).ok()) { return CasStatusCode::kNoError; } } event_listener_ = event_listener; return CasStatusCode::kNoError; } // TODO(jfore): Split out the functionality and move the callback out of this // class. CasStatus WidevineCas::openSession(WvCasSessionId* sessionId) { std::unique_lock locker(lock_); if (nullptr == sessionId) { return CasStatus(CasStatusCode::kInvalidParameter, "missing openSession sessionId"); } CasSessionPtr session = newCasSession(); CasStatus status = session->initialize( crypto_session_, reinterpret_cast(sessionId)); if (CasStatusCode::kNoError != status.status_code()) { return status; } WidevineCasSessionMap::instance().AddSession(*sessionId, session); return CasStatusCode::kNoError; } CasStatus WidevineCas::closeSession(WvCasSessionId sessionId) { std::unique_lock locker(lock_); CasSessionPtr session = WidevineCasSessionMap::instance().GetSession(sessionId); // TODO(jfore): Add a log event if the session doesn't exist and perhaps raise // an error.` if (session == nullptr) { return CasStatus(CasStatusCode::kSessionNotFound, "unknown session id"); } WidevineCasSessionMap::instance().RemoveSession(sessionId); return CasStatusCode::kNoError; } // TODO(jfore): Add unit test to widevine_cas_api_test.cpp that is added in // another cl. CasStatus WidevineCas::processEcm(WvCasSessionId sessionId, const CasEcm& ecm) { LOGD("WidevineCasPlugin::processEcm"); std::unique_lock locker(lock_); // If we don't have a license yet, save the ecm and session id. if (!has_license_) { deferred_ecms_.emplace(sessionId, ecm); return CasStatusCode::kDeferedEcmProcessing; } return HandleProcessEcm(sessionId, ecm); } CasStatus WidevineCas::HandleProcessEcm(const WvCasSessionId& sessionId, const CasEcm& ecm) { if (cas_license_->IsExpired()) { return CasStatus(CasStatusCode::kCasLicenseError, "license is expired, unable to process ecm"); } CasSessionPtr session = WidevineCasSessionMap::instance().GetSession(sessionId); if (session == nullptr) { return CasStatus(CasStatusCode::kSessionNotFound, "unknown session for processEcm"); } uint8_t ecm_age_previous = session->GetEcmAgeRestriction(); CasStatus status = session->processEcm(ecm, parental_control_age_); uint8_t ecm_age_current = session->GetEcmAgeRestriction(); if (event_listener_ != nullptr && ecm_age_current != ecm_age_previous) { event_listener_->OnAgeRestrictionUpdated(sessionId, ecm_age_current); } if (status.ok()) { cas_license_->BeginDecryption(); } return status; } CasStatus WidevineCas::HandleDeferredECMs() { for (const auto& deferred_ecm : deferred_ecms_) { CasStatus status = HandleProcessEcm(deferred_ecm.first, deferred_ecm.second); if (!status.ok()) { return status; } } deferred_ecms_.clear(); return CasStatusCode::kNoError; } CasStatus WidevineCas::generateDeviceProvisioningRequest( std::string* provisioning_request) { std::unique_lock locker(lock_); if (provisioning_request == nullptr) { return CasStatus(CasStatusCode::kInvalidParameter, "missing output buffer for provisioning request"); } return cas_license_->GenerateDeviceProvisioningRequest(provisioning_request); } CasStatus WidevineCas::handleProvisioningResponse(const std::string& response) { if (response.empty()) { return CasStatus(CasStatusCode::kCasLicenseError, "empty individualization response"); } std::string device_file; std::unique_lock locker(lock_); CasStatus status = cas_license_->HandleDeviceProvisioningResponse( response, &device_certificate_, &wrapped_rsa_key_, &device_file); if (!status.ok()) { return status; } if (!device_file.empty()) { std::string cert_filename(std::string(kBasePathPrefix) + std::string(kCertFileBase)); StoreFile(*file_system_, cert_filename, device_file); } return CasStatusCode::kNoError; } CasStatus WidevineCas::generateEntitlementRequest( const std::string& init_data, std::string* entitlement_request, std::string& license_id) { media_id_ = CasMediaId::create(); CasStatus status = media_id_->initialize(init_data); if (!status.ok()) { return status; } std::string license_file; std::string filename = GenerateLicenseFilename(media_id_->content_id(), media_id_->provider_id()); if (ReadFileFromStorage(*file_system_, filename, &license_file)) { CasStatus status = cas_license_->HandleStoredLicense(wrapped_rsa_key_, license_file); if (status.ok()) { // If license file is expired, don't proceed the request. Also // delete the stored license file. std::unique_lock locker(lock_); if (cas_license_->IsExpired()) { if (!RemoveFile(*file_system_, filename)) { return CasStatus(CasStatusCode::kInvalidLicenseFile, "unable to remove expired license file from disk"); } LOGI("Remove expired license file from disk successfully."); return CasStatus(CasStatusCode::kCasLicenseError, "license is expired, unable to process emm"); } license_id = filename.substr(0, filename.size() - strlen(kLicenseFileNameSuffix)); policy_timer_.Start(this, 1); has_license_ = true; return HandleDeferredECMs(); } LOGI("Fallthru"); } if (entitlement_request == nullptr) { return CasStatus(CasStatusCode::kInvalidParameter, "missing output buffer for entitlement request"); } std::unique_lock locker(lock_); return cas_license_->GenerateEntitlementRequest( init_data, device_certificate_, wrapped_rsa_key_, license_type_, entitlement_request); } CasStatus WidevineCas::handleEntitlementResponse(const std::string& response, std::string& license_id) { if (response.empty()) { return CasStatus(CasStatusCode::kCasLicenseError, "empty entitlement response"); } std::string device_file; std::unique_lock locker(lock_); CasStatus status = cas_license_->HandleEntitlementResponse(response, &device_file); if (status.ok()) { // A license has been successfully loaded. Load any ecms that may have been // deferred waiting for the license. has_license_ = true; status = HandleDeferredECMs(); if (!status.ok()) { return status; } policy_timer_.Start(this, 1); if (device_file.empty()) { return status; } if (!device_file.empty()) { std::string filename = GenerateLicenseFilename(media_id_->content_id(), media_id_->provider_id()); StoreFile(*file_system_, filename, device_file); // license_id will be the filename without ".lic" extension. license_id = filename.substr(0, filename.size() - strlen(kLicenseFileNameSuffix)); } } return status; } CasStatus WidevineCas::generateEntitlementRenewalRequest( std::string* entitlement_renewal_request) { if (entitlement_renewal_request == nullptr) { return CasStatus(CasStatusCode::kInvalidParameter, "missing output buffer for entitlement renewal request"); } return cas_license_->GenerateEntitlementRenewalRequest( device_certificate_, entitlement_renewal_request); } CasStatus WidevineCas::handleEntitlementRenewalResponse( const std::string& response, std::string& license_id) { if (response.empty()) { return CasStatus(CasStatusCode::kCasLicenseError, "empty entitlement renewal response"); } std::string device_file; std::unique_lock locker(lock_); CasStatus status = cas_license_->HandleEntitlementRenewalResponse(response, &device_file); if (!status.ok()) { return status; } if (!device_file.empty()) { std::string filename = GenerateLicenseFilename(media_id_->content_id(), media_id_->provider_id()); StoreFile(*file_system_, filename, device_file); // license_id will be the filename without ".lic" extension. license_id = filename.substr(0, filename.size() - std::string(".lic").size()); } return CasStatusCode::kNoError; } CasStatus WidevineCas::RemoveLicense(const std::string file_name) { // Check if the license is in use. If it is, besides removing the license, // update policy in current license. Else, we just directly remove it. if (nullptr == media_id_.get()) { return CasStatus(CasStatusCode::kCasLicenseError, "No media id"); } // Remove the license file given the file_name user provides. if (!RemoveFile(*file_system_, file_name)) { return CasStatus(CasStatusCode::kInvalidLicenseFile, "unable to remove license file from disk"); } LOGI("Remove license file from disk successfully."); std::string used_license_filename = GenerateLicenseFilename( media_id_->content_id(), media_id_->provider_id()); if (file_name.compare(used_license_filename) == 0) { // Update license policy for the in-used license. Plugin will not allowed to // play stream, store and renew license unless a new plugin instance is // created. std::unique_lock locker(lock_); cas_license_->UpdateLicenseForLicenseRemove(); } return CasStatusCode::kNoError; } bool WidevineCas::is_provisioned() const { return (!(device_certificate_.empty() || wrapped_rsa_key_.empty())); } CasStatus WidevineCas::ProcessCAPrivateData(const CasData& private_data, std::string* init_data) { if (init_data == nullptr) { return CasStatus(CasStatusCode::kInvalidParameter, "missing output buffer for init_data"); } // Parse provider and content id from CA descriptor. video_widevine::CaDescriptorPrivateData descriptor; descriptor.ParseFromArray(private_data.data(), private_data.size()); if (!descriptor.has_content_id() || !descriptor.has_provider()) { return CasStatus(CasStatusCode::kInvalidParameter, "unable to parse private data"); } // Build PSSH of type ENTITLEMENT. video_widevine::WidevinePsshData pssh; pssh.set_provider(descriptor.provider()); pssh.set_content_id(descriptor.content_id()); pssh.set_type(video_widevine::WidevinePsshData::ENTITLEMENT); pssh.SerializeToString(init_data); return CasStatusCode::kNoError; } CasStatus WidevineCas::ProcessSessionCAPrivateData(WvCasSessionId session_id, const CasData& private_data, std::string* init_data) { if (!WidevineCasSessionMap::instance().GetSession(session_id)) { return CasStatus(CasStatusCode::kCasLicenseError, "invalid session id"); } return ProcessCAPrivateData(private_data, init_data); } CasStatus WidevineCas::GetUniqueID(std::string* buffer) { return crypto_session_->GetDeviceID(buffer); } CasStatus WidevineCas::HandleStoredDrmCert(const std::string& certificate) { if (certificate.empty()) { return CasStatus(CasStatusCode::kCasLicenseError, "empty certificate data"); } CasStatus status = cas_license_->HandleStoredDrmCert( certificate, &device_certificate_, &wrapped_rsa_key_); return status; } CasStatus WidevineCas::HandleSetParentalControlAge(const CasData& data) { if (data.empty()) { return CasStatus(CasStatusCode::kCasLicenseError, "missing value of parental control min age"); } parental_control_age_ = data[0]; LOGI("Parental control age set to: ", parental_control_age_); return CasStatusCode::kNoError; } } // namespace wvcas