Files
android/libwvdrmengine/src/WVDrmFactory.cpp
Kyle Zhang afbfe173a8 Add dumping provision request functionality
[ Merge of http://go/wvgerrit/179590 ]

Bug: 277788529
Test: dumpsys android.hardware.drm.IDrmFactory/widevine -prov
Change-Id: Ife88d1323bf5ee423eb8827ca324b73db3cc984a
2023-07-27 21:49:54 +00:00

424 lines
15 KiB
C++

//
// 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 <algorithm>
#include <cctype>
#include <cinttypes>
#include <cstdio>
#include <android/binder_ibinder_platform.h>
#include <binder/IPCThreadState.h>
#include <utils/Log.h>
#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<uint8_t>& 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<WVCryptoPlugin> plugin =
::ndk::SharedRefBase::make<WVCryptoPlugin>(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<WVDrmPlugin> plugin = ::ndk::SharedRefBase::make<WVDrmPlugin>(
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<int32_t>("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<int32_t>("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<WVDrmPlugin> plugin = ndk::SharedRefBase::make<WVDrmPlugin>(
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<wvcdm::WvContentDecryptionModule> cdm(getCDM());
std::vector<wvcdm::WvMetricsSnapshot> 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<wvcdm::WvContentDecryptionModule> cdm(getCDM());
const bool isLevel1 =
cdm->IsSecurityLevelSupported(wvcdm::CdmSecurityLevel::kSecurityLevelL1);
dprintf(fd, " default_security_level: %s\n", isLevel1 ? "L1" : "L3");
const std::map<string, string> 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<std::string_view>{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