// // 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 "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::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 auto& self = android::IPCThreadState::self(); const char* sid = self->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 auto& self = ::android::IPCThreadState::self(); const char* sid = self->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}); } schemes.minLevel = schemes.maxLevel = SecurityLevel::SW_SECURE_CRYPTO; if (wvcdm::WvContentDecryptionModule::IsSecurityLevelSupported( wvcdm::kSecurityLevelL1)) { schemes.maxLevel = SecurityLevel::HW_SECURE_ALL; } schemes.mimeTypes = { 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}; *_aidl_return = schemes; return ::ndk::ScopedAStatus::ok(); } string WVDrmFactory::stringToHex(const string& input) { // If input contains punctuations that are not part of // a valid server url, we need to convert it to hex. const string validChars = "/-._~%:"; bool toHex = false; for (const char ch : input) { if (ispunct(ch) != 0 && validChars.find(ch) == string::npos) { 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, "\n**** Widevine 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)- OEM Crypto API:", wvcdm::QUERY_KEY_OEMCRYPTO_API_VERSION}, {"version(minor)- OEM Crypto API:", wvcdm::QUERY_KEY_OEMCRYPTO_API_MINOR_VERSION}, {"id- device:", wvcdm::QUERY_KEY_DEVICE_ID}, {"id- system:", 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}, {"OEM Crypto 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}, }; 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(); } } 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; } dprintf(fd, "\nDefault to print all info if no arguments are used.\n"); dprintf(fd, "Optional arguments are:\n"); dprintf(fd, "\tm:cdm metrics | p:cdm properties\n"); dprintf(fd, "Usage: adb shell lshal debug " "android.hardware.drm@1.3::IDrmFactory/widevine [m|p]\n"); 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}) { dprintf(fd, "args: %s\n", str.data()); string option = str.data(); if (option.find('m') != string::npos || option.find('M') != string::npos) { dumpCdmMetrics = true; } if (option.find('p') != string::npos || option.find('P') != string::npos) { dumpCdmProperties = true; } } } if (dumpCdmMetrics) printCdmMetrics(fd); if (dumpCdmProperties) printCdmProperties(fd); fsync(fd); return STATUS_OK; } } // namespace widevine } // namespace drm } // namespace hardware } // namespace wvdrm