// // 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 #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 "string_conversions.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; namespace { void PrintStream(int fd, std::stringstream& ss) { const std::string content = ss.str(); dprintf(fd, "%s", content.c_str()); } void FormatIndent(int fd, size_t indent) { const size_t kMaxIndent = 60; const size_t real_indent = std::min(indent, kMaxIndent); char buffer[kMaxIndent + 1]; memset(buffer, ' ', real_indent); buffer[real_indent] = 0; dprintf(fd, "%s", buffer); if (real_indent < indent) FormatIndent(fd, indent - real_indent); } void FormatWvCdmIdentifier(int fd, size_t parent_indent, const wvcdm::CdmIdentifier& identifier) { const size_t indent = parent_indent + 2; // Spoid. FormatIndent(fd, indent); dprintf(fd, "spoid: \"%s\"\n", wvutil::b2a_hex(identifier.spoid).c_str()); // Origin. FormatIndent(fd, indent); dprintf(fd, "origin: \"%s\"\n", wvutil::b2a_hex(identifier.origin).c_str()); // App package name. FormatIndent(fd, indent); dprintf(fd, "app_package_name: \"%s\"\n", identifier.app_package_name.c_str()); // Unique ID. FormatIndent(fd, indent); dprintf(fd, "unique_id: %" PRIu32 "\n", identifier.unique_id); } void FormatWvMetricsSnapshotItem(int fd, size_t parent_indent, const wvcdm::WvMetricsSnapshot& snapshot, size_t item_index) { const size_t indent = parent_indent + 2; // CDM identifier. FormatIndent(fd, parent_indent); // First parameter uses indent + list tick. dprintf(fd, "- identifier: # [%zu]\n", item_index); FormatWvCdmIdentifier(fd, indent, snapshot.cdm_id()); // Timestamp. FormatIndent(fd, indent); dprintf(fd, "timestamp: \"%s\"\n", snapshot.GetFormattedTimestamp().c_str()); // Serialized proto size. FormatIndent(fd, indent); dprintf(fd, "serialized_proto_bytes: %zu\n", snapshot.metrics().ByteSizeLong()); // Engine metrics. FormatIndent(fd, indent); dprintf(fd, "cdm_metrics:\n"); std::stringstream ss; wv_metrics::FormatWvCdmMetrics(snapshot.metrics(), indent, ss); PrintStream(fd, ss); } } // namespace bool WVDrmFactory::isCryptoSchemeSupported(const Uuid& in_uuid) { return isWidevineUUID(in_uuid.uuid.data()); } ::ndk::SpAIBinder WVDrmFactory::createBinder() { auto binder = BnDrmFactory::createBinder(); AIBinder_setRequestingSid(binder.get(), true); return binder; } ::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()); *_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()); *_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& input) { if (std::all_of(input.begin(), input.end(), ::isprint)) { 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; } // Attempt to get provisioning request. // Choose if it's cast specific. // Output the request to fd. void WVDrmFactory::printProvisioningRequest(int fd, bool cast) { dprintf(fd, "provisioning_request:\n"); std::shared_ptr plugin = ndk::SharedRefBase::make( getCDM(), "dumpsys", &sOemCryptoInterface, areSpoidsEnabled()); ::aidl::android::hardware::drm::ProvisionRequest res; std::string cert = ""; std::string cert_auth = ""; if (cast) { cert = "X.509"; cert_auth = "cast.google.com"; } plugin->getProvisionRequest(cert, cert_auth, &res); string req(res.request.begin(), res.request.end()); dprintf(fd, " request: %s\n", req.c_str()); dprintf(fd, " default_url: %s\n", res.defaultUrl.c_str()); } void WVDrmFactory::printCdmMetrics(int fd) { dprintf(fd, "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()); std::vector snapshots; bool full_list_returned = false; wvcdm::CdmResponseType result = cdm->GetAllCurrentMetricsSnapshots(&snapshots, &full_list_returned); if (result != wvcdm::NO_ERROR) { dprintf(fd, " live_metrics:\n"); dprintf(fd, " error_message: \"%s\"\n", result.ToString().c_str()); dprintf(fd, " error_code: %d\n", result.ToInt()); } else if (snapshots.empty()) { // YAML does not support empty property values. const char kNoMetricsMessage[] = "Metrics not available, please retry while streaming a video."; dprintf(fd, " live_metrics: [] # %s\n", kNoMetricsMessage); } else { dprintf(fd, " live_metrics: "); if (full_list_returned) { dprintf(fd, "# count = %zu\n", snapshots.size()); } else { const char kPartialListMessage[] = "Some metrics are missing due to an internal error, " "check logs for details."; dprintf(fd, "# count = %zu, %s\n", snapshots.size(), kPartialListMessage); } for (size_t i = 0; i < snapshots.size(); i++) { FormatWvMetricsSnapshotItem(fd, 2, snapshots[i], i); } } // Saved metrics. snapshots.clear(); result = cdm->GetAllSavedMetricsSnapshots(&snapshots); if (result != wvcdm::NO_ERROR) { dprintf(fd, " past_metrics:\n"); dprintf(fd, " error_message: \"%s\"\n", result.ToString().c_str()); dprintf(fd, " error_code: %d\n", result.ToInt()); } else if (snapshots.empty()) { // YAML does not support empty property values. const char kNoMetricsMessage[] = "Metrics not available, no instances have been created."; dprintf(fd, " past_metrics: [] # %s\n", kNoMetricsMessage); } else { dprintf(fd, " past_metrics: # count = %zu\n", snapshots.size()); for (size_t i = 0; i < snapshots.size(); i++) { FormatWvMetricsSnapshotItem(fd, 2, snapshots[i], i); } } } void WVDrmFactory::printCdmProperties(int fd) { dprintf(fd, "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_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}, // Debug properties. Not exposed to app. {"boot_certificate_chain", wvcdm::QUERY_KEY_DEBUG_BOOT_CERTIFICATE_CHAIN}, {"device_information", wvcdm::QUERY_KEY_DEVICE_INFORMATION}, }; 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; bool dumpProvReq = false, provCast = 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("-prov_cast") != string::npos || option.find("-Prov_cast") != string::npos) { dumpProvReq = true; provCast = true; } else if (option.find("-prov") != string::npos || option.find("-Prov") != string::npos) { dumpProvReq = 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 (dumpCdmProperties) printCdmProperties(fd); if (dumpProvReq) printProvisioningRequest(fd, provCast); if (dumpCdmMetrics) printCdmMetrics(fd); fsync(fd); return STATUS_OK; } } // namespace widevine } // namespace drm } // namespace hardware } // namespace wvdrm