// // Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. // //#define LOG_NDEBUG 0 #define LOG_TAG "WVCdm-DrmFactory" #include "WVDrmFactory.h" #include #include #include #include #include "Utils.h" #include "WVCDMSingleton.h" #include "WVCryptoPlugin.h" #include "WVDrmPlugin.h" #include "WVUUID.h" #include "android-base/properties.h" #include "cutils/properties.h" #include "wv_cdm_constants.h" #include "wv_content_decryption_module.h" #include "wv_metrics.h" namespace wvdrm { namespace hardware { namespace drm { namespace widevine { using std::string; using std::vector; using ::aidl::android::hardware::drm::CryptoSchemes; using ::aidl::android::hardware::drm::SecurityLevel; using ::aidl::android::hardware::drm::Status; using ::aidl::android::hardware::drm::SupportedContentType; using ::aidl::android::hardware::drm::Uuid; WVGenericCryptoInterface WVDrmFactory::sOemCryptoInterface; bool WVDrmFactory::isCryptoSchemeSupported(const Uuid& in_uuid) { return isWidevineUUID(in_uuid.uuid.data()); } ::ndk::ScopedAStatus WVDrmFactory::createCryptoPlugin( const ::aidl::android::hardware::drm::Uuid& in_uuid, const std::vector& in_initData, std::shared_ptr<::aidl::android::hardware::drm::ICryptoPlugin>* _aidl_return) { const char* sid = AIBinder_getCallingSid(); sid = sid ? (std::strstr(sid, "mediadrmserver") ? sid : "app") : "nullptr"; ALOGI("[%s] calling %s", sid, __PRETTY_FUNCTION__); if (!isCryptoSchemeSupported(in_uuid)) { ALOGE( "Widevine Drm HAL: failed to create crypto plugin, " "invalid crypto scheme"); *_aidl_return = nullptr; return toNdkScopedAStatus(Status::ERROR_DRM_CANNOT_HANDLE); } std::shared_ptr plugin = ::ndk::SharedRefBase::make(in_initData.data(), in_initData.size(), getCDM()); AIBinder_setRequestingSid(plugin->asBinder().get(), true); *_aidl_return = std::move(plugin); return toNdkScopedAStatus(Status::OK); } ::ndk::ScopedAStatus WVDrmFactory::createDrmPlugin( const Uuid& in_uuid, const string& in_appPackageName, std::shared_ptr<::aidl::android::hardware::drm::IDrmPlugin>* _aidl_return) { const char* sid = AIBinder_getCallingSid(); sid = sid ? (std::strstr(sid, "mediadrmserver") ? sid : "app") : "nullptr"; ALOGI("[%s][%s] calling %s", sid, in_appPackageName.c_str(), __PRETTY_FUNCTION__); if (!isCryptoSchemeSupported(in_uuid)) { ALOGE( "Widevine Drm HAL: failed to create drm plugin, " "invalid crypto scheme"); *_aidl_return = nullptr; return toNdkScopedAStatus(Status::ERROR_DRM_CANNOT_HANDLE); } if (!isBlankAppPackageNameAllowed() && in_appPackageName.empty()) { ALOGE( "Widevine Drm HAL: Failed to create DRM Plugin, blank App Package " "Name disallowed."); *_aidl_return = nullptr; return toNdkScopedAStatus(Status::ERROR_DRM_CANNOT_HANDLE); } std::shared_ptr plugin = ::ndk::SharedRefBase::make( getCDM(), in_appPackageName.c_str(), &sOemCryptoInterface, areSpoidsEnabled()); AIBinder_setRequestingSid(plugin->asBinder().get(), true); *_aidl_return = plugin; return toNdkScopedAStatus(Status::OK); } bool WVDrmFactory::areSpoidsEnabled() { return firstApiLevel() >= 26; // Android O } bool WVDrmFactory::isBlankAppPackageNameAllowed() { return firstApiLevel() < 29; // Android Q } int32_t WVDrmFactory::firstApiLevel() { // Check what this device's first API level was. int32_t firstApiLevel = android::base::GetIntProperty("ro.product.first_api_level", 0); if (firstApiLevel == 0) { // First API Level is 0 on factory ROMs, but we can assume the current SDK // version is the first if it's a factory ROM. firstApiLevel = android::base::GetIntProperty("ro.build.version.sdk", 0); } return firstApiLevel; } ::ndk::ScopedAStatus WVDrmFactory::getSupportedCryptoSchemes( CryptoSchemes* _aidl_return) { CryptoSchemes schemes{}; for (const auto& uuid : wvdrm::getSupportedCryptoSchemes()) { schemes.uuids.push_back({uuid}); } bool isL1 = wvcdm::WvContentDecryptionModule::IsSecurityLevelSupported(wvcdm::kSecurityLevelL1); for (auto mime : {wvcdm::ISO_BMFF_VIDEO_MIME_TYPE, wvcdm::ISO_BMFF_AUDIO_MIME_TYPE, wvcdm::WEBM_VIDEO_MIME_TYPE, wvcdm::WEBM_AUDIO_MIME_TYPE, wvcdm::CENC_INIT_DATA_FORMAT, wvcdm::HLS_INIT_DATA_FORMAT, wvcdm::WEBM_INIT_DATA_FORMAT}) { bool isAudio = wvcdm::WvContentDecryptionModule::IsAudio(mime); SupportedContentType ct{mime, SecurityLevel::SW_SECURE_CRYPTO, SecurityLevel::SW_SECURE_DECODE}; if (isL1) { ct.maxLevel = isAudio ? SecurityLevel::HW_SECURE_DECODE : SecurityLevel::HW_SECURE_ALL; } schemes.mimeTypes.push_back(ct); } *_aidl_return = schemes; return ::ndk::ScopedAStatus::ok(); } string WVDrmFactory::stringToHex(const string& s) { string input(s.c_str()); bool toHex = false; for (const char ch : input) { if (!isprint(ch)) { toHex = true; break; } } if (!toHex) return input; static constexpr char hex[] = "0123456789ABCDEF"; string output; output.reserve(input.length() * 2); for (const unsigned char ch : input) { output.push_back(hex[ch >> 4]); output.push_back(hex[ch & 15]); } return output; } void WVDrmFactory::printCdmMetrics(int fd) { dprintf(fd, "\n**** Widevine Cdm Metrics ****\n"); // Verify that the version of the library that we linked against is // compatible with the version of the headers we compiled against. GOOGLE_PROTOBUF_VERIFY_VERSION; android::sp cdm(getCDM()); vector metrics; bool full_list_returned = true; wvcdm::CdmResponseType result = cdm->GetMetrics(&metrics, &full_list_returned); if (metrics.empty()) { dprintf(fd, "Metrics not available, please retry while streaming a video\n"); } else if (!full_list_returned) { dprintf(fd, "Not all metrics are returned due to some GetMetric error, " "please check logcat for possible GetMetric errors.\n"); } if (result == wvcdm::NO_ERROR) { for (auto& metric : metrics) { dprintf(fd, "*** Metric size=%zu\n", metric.DebugString().size()); string formatted; wv_metrics::FormatWvCdmMetrics(metric, formatted); dprintf(fd, "%s\n", formatted.c_str()); } } else { dprintf(fd, "GetMetrics failed, error=%d\n", result); } } void WVDrmFactory::printCdmProperties(int fd) { dprintf(fd, "\nwidevine_cdm_properties:\n"); android::sp cdm(getCDM()); const bool isLevel1 = cdm->IsSecurityLevelSupported(wvcdm::CdmSecurityLevel::kSecurityLevelL1); dprintf(fd, " default_security_level: '%s'\n", isLevel1 ? "L1" : "L3"); const std::map cdmProperties = { {"version_widevine_cdm", wvcdm::QUERY_KEY_WVCDM_VERSION}, {"version_current_srm", wvcdm::QUERY_KEY_CURRENT_SRM_VERSION}, {"version_major_oemcrypto_api", wvcdm::QUERY_KEY_OEMCRYPTO_API_VERSION}, {"version_minor_oemcrypto_api", wvcdm::QUERY_KEY_OEMCRYPTO_API_MINOR_VERSION}, {"device_id", wvcdm::QUERY_KEY_DEVICE_ID}, {"system_id", wvcdm::QUERY_KEY_SYSTEM_ID}, {"renewal_server_url", wvcdm::QUERY_KEY_RENEWAL_SERVER_URL}, {"hdcp_level_max", wvcdm::QUERY_KEY_MAX_HDCP_LEVEL}, {"hdcp_level_current", wvcdm::QUERY_KEY_CURRENT_HDCP_LEVEL}, {"num_sessions_max_supported", wvcdm::QUERY_KEY_MAX_NUMBER_OF_SESSIONS}, {"num_sessions_opened", wvcdm::QUERY_KEY_NUMBER_OF_OPEN_SESSIONS}, {"resource_rating_tier", wvcdm::QUERY_KEY_RESOURCE_RATING_TIER}, {"support_decrypt_hash", wvcdm::QUERY_KEY_DECRYPT_HASH_SUPPORT}, {"support_SRM_update", wvcdm::QUERY_KEY_SRM_UPDATE_SUPPORT}, {"support_usage_table", wvcdm::QUERY_KEY_USAGE_SUPPORT}, {"max_usage_table_entries", wvcdm::QUERY_KEY_MAX_USAGE_TABLE_ENTRIES}, {"oemcrypto_build_info", wvcdm::QUERY_KEY_OEMCRYPTO_BUILD_INFORMATION}, {"provisioning_id", wvcdm::QUERY_KEY_PROVISIONING_ID}, {"provisioning_model", wvcdm::QUERY_KEY_PROVISIONING_MODEL}, {"analog_capabilities", wvcdm::QUERY_KEY_ANALOG_OUTPUT_CAPABILITIES}, {"can_disable_analog_output", wvcdm::QUERY_KEY_CAN_DISABLE_ANALOG_OUTPUT}, {"watermarking_support", wvcdm::QUERY_KEY_WATERMARKING_SUPPORT}, {"production_ready", wvcdm::QUERY_KEY_PRODUCTION_READY}, }; string value; for (const auto& property : cdmProperties) { cdm->QueryStatus(wvcdm::RequestedSecurityLevel::kLevelDefault, property.second, &value); string outString = stringToHex(value); dprintf(fd, " %s: '%s'\n", property.first.c_str(), outString.c_str()); value.clear(); } } void WVDrmFactory::printUsage(int fd) { dprintf(fd, "\nDefault to print all info if no arguments are used.\n"); dprintf(fd, "Optional arguments are:\n"); dprintf(fd, "\ta:print all info | m:cdm metrics | p:cdm properties\n"); dprintf(fd, "Usage: adb shell dumpsys " "android.hardware.drm::IDrmFactory/widevine [-a|-h|-m|-p]\n"); } binder_status_t WVDrmFactory::dump(int fd, const char** args, uint32_t numArgs) { if (fd < 0) { ALOGE("%s: missing fd for writing", __FUNCTION__); return STATUS_BAD_VALUE; } bool dumpCdmProperties, dumpCdmMetrics = false; if (numArgs == 0) { // default to print all info if no arguments are given dumpCdmProperties = dumpCdmMetrics = true; } else { for (auto&& str : std::vector{args, args + numArgs}) { string option = str.data(); if (option.find("-a") != string::npos || option.find("-A") != string::npos) { dumpCdmProperties = dumpCdmMetrics = true; } else if (option.find("-m") != string::npos || option.find("-M") != string::npos) { dumpCdmMetrics = true; } else if (option.find("-p") != string::npos || option.find("-P") != string::npos) { dumpCdmProperties = true; } else if (option.find("-h") != string::npos || option.find("-H") != string::npos) { printUsage(fd); } else { dprintf(fd, "Invalid arg: %s\n", option.c_str()); printUsage(fd); } } } if (dumpCdmMetrics) printCdmMetrics(fd); if (dumpCdmProperties) printCdmProperties(fd); fsync(fd); return STATUS_OK; } } // namespace widevine } // namespace drm } // namespace hardware } // namespace wvdrm