Files
android/libwvdrmengine/cdm/core/src/client_identification.cpp
Rahul Frias 2f83cd0e49 Correct stability issues for SPOIDs for provisioning 4.0
[ Merge of http://go/wvgerrit/183472 ]

For provisioning 4.0 devices, the DRM certificate serial number
was changing on a reprovisioning attempt or factory reset. The
app parameters sent up in the client identification name-value
pair field were being filtered out in provisioning requests.
This has been corrected for provisioning 4.0 stage 2
(DRM certificate request). There is no need to include them for
stage 1 (OEM certificate request).

The test case WvCdmRequestLicenseTest.ProvisioningSpoidTest
was created earlier to ensure that SPOIDs and DRM certificates are
stable. Unfortunately due to another bug b/250099615, the RKP service
was holding a connection to the Widevine TA for provisioning 4.0
devices. When native tests ran as their own process, L1 would fail
to load due to a connection failure and the test would run as L3.
The tests passed for provisioning 4.0 devices Pixel 7 and 8 when
they should have failed. This gave us a false sense of confidence
that the SPOIDs were stable.

For now a workaround is to run a shell command to kill the widevine
TA before running native tests.

$ adb shell pkill -f -9 widevine

New tests have been introduced to provide integration coverage
WVPluginTest at the WV plugin level and CoreIntegrationTest
for core. GTS tests are also being written in b/295538002.

Bug: 294451432
Bug: 293950895
Test: WVPluginTest.ProvisioningStableSpoidTestL1, WVTS tests
Change-Id: Ib9ace4387866ea38bb1840feb69cea78d2d2c09c
2023-09-19 09:39:13 -07:00

400 lines
14 KiB
C++

// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine License
// Agreement.
#include "client_identification.h"
#include <string>
#include "crypto_session.h"
#include "license_protocol.pb.h"
#include "log.h"
#include "properties.h"
#include "string_conversions.h"
#include "wv_cdm_constants.h"
namespace wvcdm {
namespace {
const std::string kKeyCompanyName = "company_name";
const std::string kKeyModelName = "model_name";
const std::string kKeyModelYear = "model_year";
const std::string kKeyArchitectureName = "architecture_name";
const std::string kKeyDeviceName = "device_name";
const std::string kKeyProductName = "product_name";
const std::string kKeyBuildInfo = "build_info";
const std::string kKeyWvCdmVersion = "widevine_cdm_version";
const std::string kKeyOemCryptoSecurityPatchLevel =
"oem_crypto_security_patch_level";
const std::string kKeyOemCryptoBuildInformation =
"oem_crypto_build_information";
// These client identification keys are used by the CDM for relaying
// important device information that cannot be overwritten by the app.
const std::array<std::string, 10> kReservedProperties = {
kKeyCompanyName,
kKeyModelName,
kKeyModelYear,
kKeyArchitectureName,
kKeyDeviceName,
kKeyProductName,
kKeyBuildInfo,
kKeyWvCdmVersion,
kKeyOemCryptoSecurityPatchLevel,
kKeyOemCryptoBuildInformation,
// TODO(b/148813171,b/142280599): include "origin" and "application_name"
// to this list once collection of this information has been moved
// to the core CDM.
};
// Checks if the client-provided |prop_name| is reserved for CDM device
// identification with the license server. Property keys which are
// reserved should be dropped from the request.
bool IsPropertyKeyReserved(const std::string& prop_name) {
for (const std::string& reserved_prop_name : kReservedProperties) {
if (prop_name == reserved_prop_name) return true;
}
return false;
}
} // namespace
// Protobuf generated classes.
using ClientCapabilities =
video_widevine::ClientIdentification::ClientCapabilities;
using AnalogOutputCapabilities = ClientCapabilities::AnalogOutputCapabilities;
using video_widevine::ClientIdentification_NameValue;
using video_widevine::EncryptedClientIdentification;
using video_widevine::ProvisioningOptions;
using video_widevine::ProvisioningRequest;
using video_widevine::ProvisioningResponse;
using video_widevine::SignedProvisioningMessage;
CdmResponseType ClientIdentification::InitForProvisioningRequest(
const std::string& client_token, CryptoSession* crypto_session) {
if (crypto_session == nullptr) {
LOGE("Crypto session not provided");
return CdmResponseType(PARAMETER_NULL);
}
is_license_request_ = false;
client_token_ = client_token;
crypto_session_ = crypto_session;
return CdmResponseType(NO_ERROR);
}
CdmResponseType ClientIdentification::InitForLicenseRequest(
const std::string& client_token, CryptoSession* crypto_session) {
if (crypto_session == nullptr) {
LOGE("Crypto session not provided");
return CdmResponseType(PARAMETER_NULL);
}
if (client_token.empty()) {
LOGE("Client token is empty");
return CdmResponseType(PARAMETER_NULL);
}
is_license_request_ = true;
client_token_ = client_token;
crypto_session_ = crypto_session;
return CdmResponseType(NO_ERROR);
}
CdmResponseType ClientIdentification::InitForOtaKeyboxProvisioning(
CryptoSession* crypto_session) {
if (crypto_session == nullptr) {
LOGE("Crypto session not provided");
return CdmResponseType(PARAMETER_NULL);
}
is_okp_request_ = true;
crypto_session_ = crypto_session;
return CdmResponseType(NO_ERROR);
}
/*
* Return the ClientIdentification message token type for provisioning request.
* NOTE: a DRM Cert should never be presented to the provisioning server.
*/
CdmResponseType ClientIdentification::Prepare(
const CdmAppParameterMap& app_parameters,
const std::string& provider_client_token,
video_widevine::ClientIdentification* client_id) {
if (is_license_request_) {
client_id->set_type(
video_widevine::ClientIdentification::DRM_DEVICE_CERTIFICATE);
client_id->set_token(client_token_);
} else if (!client_token_.empty()) {
// A token has been provided via InitForProvisioningRequest. This can only
// happen in provisioning 4 (second stage) where an OEM cert is provided.
client_id->set_type(
video_widevine::ClientIdentification::OEM_DEVICE_CERTIFICATE);
client_id->set_token(client_token_);
} else if (!is_okp_request_) {
// An OTA Keybox Provisioning request does not have a client id.
// Otherwise this is a regular provisioning request.
video_widevine::ClientIdentification::TokenType token_type;
if (!GetProvisioningTokenType(&token_type)) {
LOGE("Failed to get provisioning token type");
return CdmResponseType(CLIENT_IDENTIFICATION_TOKEN_ERROR_1);
}
client_id->set_type(token_type);
std::string token;
std::string additional_token;
CdmResponseType status =
crypto_session_->GetProvisioningToken(&token, &additional_token);
if (status != NO_ERROR) {
LOGE("Failed to get provisioning token: status = %d", status.ToInt());
return status;
}
client_id->set_token(token);
if (!additional_token.empty()) {
client_id->mutable_device_credentials()->set_token(additional_token);
}
}
ClientIdentification_NameValue* client_info;
// Include app parameters for license and provisioning requests
if (!is_okp_request_) {
CdmAppParameterMap::const_iterator iter;
for (iter = app_parameters.begin(); iter != app_parameters.end(); ++iter) {
if (IsPropertyKeyReserved(iter->first)) {
LOGD("Discarding client property: name = \"%s\", value = \"%s\"",
iter->first.c_str(), iter->second.c_str());
continue;
}
client_info = client_id->add_client_info();
client_info->set_name(iter->first);
client_info->set_value(iter->second);
}
}
std::string value;
if (Properties::GetCompanyName(&value)) {
client_info = client_id->add_client_info();
client_info->set_name(kKeyCompanyName);
client_info->set_value(value);
}
if (Properties::GetModelName(&value)) {
client_info = client_id->add_client_info();
client_info->set_name(kKeyModelName);
client_info->set_value(value);
}
if (Properties::GetModelYear(&value)) {
client_info = client_id->add_client_info();
client_info->set_name(kKeyModelYear);
client_info->set_value(value);
}
if (Properties::GetArchitectureName(&value)) {
client_info = client_id->add_client_info();
client_info->set_name(kKeyArchitectureName);
client_info->set_value(value);
}
if (Properties::GetDeviceName(&value)) {
client_info = client_id->add_client_info();
client_info->set_name(kKeyDeviceName);
client_info->set_value(value);
}
if (Properties::GetProductName(&value)) {
client_info = client_id->add_client_info();
client_info->set_name(kKeyProductName);
client_info->set_value(value);
}
if (Properties::GetBuildInfo(&value)) {
client_info = client_id->add_client_info();
client_info->set_name(kKeyBuildInfo);
client_info->set_value(value);
}
if (Properties::GetWVCdmVersion(&value)) {
client_info = client_id->add_client_info();
client_info->set_name(kKeyWvCdmVersion);
client_info->set_value(value);
}
client_info = client_id->add_client_info();
client_info->set_name(kKeyOemCryptoSecurityPatchLevel);
client_info->set_value(
std::to_string((uint32_t)crypto_session_->GetSecurityPatchLevel()));
std::string oec_build_info;
if (crypto_session_->GetBuildInformation(&oec_build_info)) {
client_info = client_id->add_client_info();
client_info->set_name(kKeyOemCryptoBuildInformation);
client_info->set_value(oec_build_info);
}
if (!provider_client_token.empty()) {
client_id->set_provider_client_token(provider_client_token);
}
if (is_okp_request_) {
// Capabilities is not important for OTA keybox provisionining.
return CdmResponseType(NO_ERROR);
}
ClientCapabilities* client_capabilities =
client_id->mutable_client_capabilities();
client_capabilities->set_client_token(true);
if (is_license_request_) {
bool supports_usage_table;
if (crypto_session_->HasUsageTableSupport(&supports_usage_table)) {
client_capabilities->set_session_token(supports_usage_table);
}
client_capabilities->set_anti_rollback_usage_table(
crypto_session_->IsAntiRollbackHwPresent());
}
uint32_t api_version = 0;
if (crypto_session_->GetApiVersion(&api_version)) {
client_capabilities->set_oem_crypto_api_version(api_version);
}
if (is_license_request_) {
CryptoSession::HdcpCapability current_version, max_version;
if (crypto_session_->GetHdcpCapabilities(&current_version, &max_version) ==
NO_ERROR) {
switch (max_version) {
case HDCP_NONE:
client_capabilities->set_max_hdcp_version(
ClientCapabilities::HDCP_NONE);
break;
case HDCP_V1:
client_capabilities->set_max_hdcp_version(
ClientCapabilities::HDCP_V1);
break;
case HDCP_V2:
client_capabilities->set_max_hdcp_version(
ClientCapabilities::HDCP_V2);
break;
case HDCP_V2_1:
client_capabilities->set_max_hdcp_version(
ClientCapabilities::HDCP_V2_1);
break;
case HDCP_V2_2:
client_capabilities->set_max_hdcp_version(
ClientCapabilities::HDCP_V2_2);
break;
case HDCP_V2_3:
client_capabilities->set_max_hdcp_version(
ClientCapabilities::HDCP_V2_3);
break;
case HDCP_NO_DIGITAL_OUTPUT:
client_capabilities->set_max_hdcp_version(
ClientCapabilities::HDCP_NO_DIGITAL_OUTPUT);
break;
default:
LOGW("Unexpected HDCP max capability version: max_version = %d",
static_cast<int>(max_version));
}
}
}
CryptoSession::SupportedCertificateTypes supported_certs;
if (crypto_session_->GetSupportedCertificateTypes(&supported_certs)) {
if (supported_certs.rsa_2048_bit) {
client_capabilities->add_supported_certificate_key_type(
ClientCapabilities::RSA_2048);
}
if (supported_certs.rsa_3072_bit) {
client_capabilities->add_supported_certificate_key_type(
ClientCapabilities::RSA_3072);
}
if (supported_certs.ecc_secp256r1) {
client_capabilities->add_supported_certificate_key_type(
ClientCapabilities::ECC_SECP256R1);
}
if (supported_certs.ecc_secp384r1) {
client_capabilities->add_supported_certificate_key_type(
ClientCapabilities::ECC_SECP384R1);
}
if (supported_certs.ecc_secp521r1) {
client_capabilities->add_supported_certificate_key_type(
ClientCapabilities::ECC_SECP521R1);
}
}
if (is_license_request_) {
client_capabilities->set_can_update_srm(false);
uint16_t srm_version;
if (crypto_session_->GetSrmVersion(&srm_version) == NO_ERROR)
client_capabilities->set_srm_version(srm_version);
}
bool can_support_output;
bool can_disable_output;
bool can_support_cgms_a;
if (crypto_session_->GetAnalogOutputCapabilities(
&can_support_output, &can_disable_output, &can_support_cgms_a)) {
AnalogOutputCapabilities capabilities =
ClientCapabilities::ANALOG_OUTPUT_NONE;
if (can_support_output) {
if (can_support_cgms_a) {
capabilities = ClientCapabilities::ANALOG_OUTPUT_SUPPORTS_CGMS_A;
} else {
capabilities = ClientCapabilities::ANALOG_OUTPUT_SUPPORTED;
}
}
client_capabilities->set_analog_output_capabilities(capabilities);
client_capabilities->set_can_disable_analog_output(can_disable_output);
} else {
client_capabilities->set_analog_output_capabilities(
ClientCapabilities::ANALOG_OUTPUT_UNKNOWN);
}
if (api_version >= OEM_CRYPTO_API_VERSION_SUPPORTS_RESOURCE_RATING_TIER) {
uint32_t tier;
if (crypto_session_->GetResourceRatingTier(&tier)) {
client_capabilities->set_resource_rating_tier(tier);
}
}
if (is_license_request_) {
CdmWatermarkingSupport support;
if (!crypto_session_->GetWatermarkingSupport(&support)) {
// Assume not supported.
support = kWatermarkingNotSupported;
}
switch (support) {
case kWatermarkingNotSupported:
client_capabilities->set_watermarking_support(
ClientCapabilities::WATERMARKING_NOT_SUPPORTED);
break;
case kWatermarkingConfigurable:
client_capabilities->set_watermarking_support(
ClientCapabilities::WATERMARKING_CONFIGURABLE);
break;
case kWatermarkingAlwaysOn:
client_capabilities->set_watermarking_support(
ClientCapabilities::WATERMARKING_ALWAYS_ON);
break;
}
}
if (api_version >=
OEM_CRYPTO_API_VERSION_SUPPORTS_INITIAL_RENEWAL_DELAY_BASE) {
client_capabilities->set_initial_renewal_delay_base(true);
}
return CdmResponseType(NO_ERROR);
}
bool ClientIdentification::GetProvisioningTokenType(
video_widevine::ClientIdentification::TokenType* token_type) {
CdmClientTokenType token = crypto_session_->GetPreProvisionTokenType();
switch (token) {
case kClientTokenKeybox:
*token_type = video_widevine::ClientIdentification::KEYBOX;
return true;
case kClientTokenOemCert:
*token_type =
video_widevine::ClientIdentification::OEM_DEVICE_CERTIFICATE;
return true;
case kClientTokenBootCertChain:
*token_type =
video_widevine::ClientIdentification::BOOT_CERTIFICATE_CHAIN;
return true;
case kClientTokenDrmCert:
default:
// shouldn't happen
LOGE("Unexpected provisioning type: %d", static_cast<int>(token));
return false;
}
}
} // namespace wvcdm