Files
ce_cdm/core/test/message_dumper.cpp
2025-04-02 10:27:18 -07:00

491 lines
21 KiB
C++

// Copyright 2023 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine License
// Agreement.
#include "message_dumper.h"
#include <iomanip>
#include "core_message_deserialize.h"
#include "license_request.h"
#include "odk.h"
#include "odk_message.h"
#include "odk_serialize.h"
#include "odk_structs.h"
#include "odk_structs_priv.h"
#include "oec_device_features.h"
#include "test_base.h"
using video_widevine::License;
using video_widevine::LicenseRequest;
using video_widevine::SignedMessage;
using video_widevine::SignedProvisioningMessage;
namespace wvcdm {
namespace {
void DumpHeader(std::ofstream* out, const std::string& type) {
const ::testing::TestInfo* const test_info =
::testing::UnitTest::GetInstance()->current_test_info();
const std::string suite = test_info->test_case_name();
const std::string name = test_info->name();
std::string new_test_name = suite + "_" + name;
// Replace the slashes with underscores so we can use it as a name again.
std::replace(new_test_name.begin(), new_test_name.end(), '/', '_');
*out << "\nTEST_F(ODKGolden" << type << "V" << ODK_MAJOR_VERSION << ", "
<< new_test_name << ") {\n";
}
void DumpHex(std::ofstream* out, const std::string& name,
const std::string& value) {
const auto out_flags = out->flags();
*out << "const uint8_t " << name << "_raw[] = {\n";
*out << " ";
for (size_t i = 0; i < value.length(); i++) {
if ((i > 0) && (i % 10 == 0)) *out << "\n ";
uint8_t c = value[i];
*out << "0x" << std::hex << std::setw(2) << std::setfill('0') << int(c)
<< ", ";
}
*out << "\n};\n";
*out << name << "_ = std::string (\n"
<< " reinterpret_cast<const char *>(" << name << "_raw), \n"
<< " sizeof(" << name << "_raw));\n";
out->flags(out_flags); // Restore flags when we're done.
}
void LogTimer(const char* field, bool set, int64_t time) {
long long total_seconds = static_cast<long long>(time);
char sign = ' ';
if (total_seconds < 0) {
total_seconds = -total_seconds;
sign = '-';
}
const unsigned long long seconds = total_seconds % 60;
unsigned long long minutes = total_seconds / 60;
unsigned long long hours = minutes / 60;
minutes = minutes % 60;
unsigned long long days = hours / 24;
hours = hours % 24;
if (set) {
LOGD("%25.25s: %c%llu seconds = %llu day %02llu:%02llu:%02llu HMS", field,
sign, total_seconds, days, hours, minutes, seconds);
} else {
LOGD("%25.25s: %llu seconds (unset)", field, total_seconds);
}
}
void PrintRequestProto(const char* label,
const video_widevine::LicenseRequest& request) {
switch (request.type()) {
case video_widevine::LicenseRequest_RequestType_NEW:
LOGD("%s type: NEW", label);
break;
case video_widevine::LicenseRequest_RequestType_RENEWAL:
LOGD("%s type: RENEWAL", label);
break;
case video_widevine::LicenseRequest_RequestType_RELEASE:
LOGD("%s type: RELEASE", label);
break;
}
}
void PrintLicenseProto(const char* label,
const video_widevine::License& license) {
LOGD("%s: can_play = %s, can_persist = %s, can_renew = %s", label,
license.policy().can_play() ? "true" : "false",
license.policy().can_persist() ? "true" : "false",
license.policy().can_renew() ? "true" : "false");
#define QUOTE_DEFINE(A) #A
#define QUOTE(A) QUOTE_DEFINE(A)
#define LOG_TIMER(NAME) \
LogTimer(QUOTE(NAME), license.policy().has_##NAME(), license.policy().NAME())
LOG_TIMER(rental_duration_seconds);
LOG_TIMER(playback_duration_seconds);
LOG_TIMER(license_duration_seconds);
LOG_TIMER(renewal_recovery_duration_seconds);
LOG_TIMER(renewal_delay_seconds);
LOG_TIMER(renewal_retry_interval_seconds);
LOGD("renewal_server_url: %s", license.policy().renewal_server_url().c_str());
LOGD("renew_with_usage: %s",
license.policy().renew_with_usage() ? "true" : "false");
LOG_TIMER(play_start_grace_period_seconds);
}
} // namespace
std::ofstream MessageDumper::license_file;
std::ofstream MessageDumper::renewal_file;
std::ofstream MessageDumper::provision_file;
void MessageDumper::SetUp() {
LOGD("Creating golden data files for ODK golden data tests.");
license_file.open("license_data.cpp");
if (!license_file) LOGE("Could not open dump file license_data.cpp");
renewal_file.open("renewal_data.cpp");
if (!renewal_file) LOGE("Could not open dump file renewal_data.cpp");
provision_file.open("provision_data.cpp");
if (!provision_file) LOGE("Could not open dump file provision_data.cpp");
}
void MessageDumper::TearDown() {
LOGD("Closing golden data files.");
license_file.close();
renewal_file.close();
provision_file.close();
}
void MessageDumper::PrintLicenseRequest(const CdmKeyRequest& request) {
SignedMessage signed_message;
ASSERT_TRUE(signed_message.ParseFromString(request.message));
if (wvoec::global_features.api_version >= wvoec::kCoreMessagesAPI) {
ASSERT_TRUE(signed_message.has_oemcrypto_core_message());
const std::string& core_request = signed_message.oemcrypto_core_message();
oemcrypto_core_message::ODK_LicenseRequest core_request_data;
ASSERT_TRUE(
oemcrypto_core_message::deserialize::CoreLicenseRequestFromMessage(
core_request, &core_request_data));
// Print core message information for the license request.
std::cout << std::endl << "License Request Information:" << std::endl;
std::cout << " License Request Core Version = "
<< core_request_data.api_major_version << "."
<< core_request_data.api_minor_version << std::endl;
std::cout << " Nonce = " << core_request_data.nonce << std::endl;
std::cout << " Session ID = " << core_request_data.session_id << std::endl;
}
}
void MessageDumper::PrintLicenseResponse(const std::string& response) {
SignedMessage signed_response;
ASSERT_TRUE(signed_response.ParseFromString(response));
if (wvoec::global_features.api_version >= wvoec::kCoreMessagesAPI) {
ASSERT_TRUE(signed_response.has_oemcrypto_core_message());
video_widevine::License license;
ASSERT_TRUE(license.ParseFromString(signed_response.msg()));
std::string message =
signed_response.oemcrypto_core_message() + signed_response.msg();
ODK_Message odk_msg = ODK_Message_Create(
reinterpret_cast<uint8_t*>(const_cast<char*>(message.c_str())),
message.length());
ODK_Message_SetSize(&odk_msg,
signed_response.oemcrypto_core_message().length());
ODK_ParsedLicense odk_parsed_license = {};
ODK_LicenseResponse odk_license_response = {};
odk_license_response.parsed_license = &odk_parsed_license;
Unpack_ODK_LicenseResponse(&odk_msg, &odk_license_response);
ASSERT_EQ(ODK_Message_GetStatus(&odk_msg), MESSAGE_STATUS_OK);
// Print core message information for the license response.
std::cout << std::endl << "License Response Information:" << std::endl;
std::cout
<< " License Response Core Version = "
<< odk_license_response.core_message.nonce_values.api_major_version
<< "."
<< odk_license_response.core_message.nonce_values.api_minor_version
<< std::endl;
std::cout << " Nonce = "
<< odk_license_response.core_message.nonce_values.nonce
<< std::endl;
std::cout << " Session ID = "
<< odk_license_response.core_message.nonce_values.session_id
<< std::endl;
}
}
void MessageDumper::DumpLicenseRequest(const CdmKeyRequest& request) {
SignedMessage signed_message;
DumpHeader(&license_file, "License");
EXPECT_TRUE(signed_message.ParseFromString(request.message));
if (wvoec::global_features.api_version >= wvoec::kCoreMessagesAPI) {
EXPECT_TRUE(signed_message.has_oemcrypto_core_message());
DumpHex(&license_file, "core_request",
signed_message.oemcrypto_core_message());
}
// Since this is run within a test, we can also verify that the
// request is valid.
video_widevine::LicenseRequest license_request;
EXPECT_TRUE(license_request.ParseFromString(signed_message.msg()));
// TODO(fredgc): figure out if we can build tests with full protobufs
// instead of proto lite, so that we can use TextFormat.
PrintRequestProto("License Request", license_request);
}
void MessageDumper::DumpLicense(const std::string& response) {
SignedMessage signed_response;
EXPECT_TRUE(signed_response.ParseFromString(response));
if (wvoec::global_features.api_version >= wvoec::kCoreMessagesAPI) {
EXPECT_TRUE(signed_response.has_oemcrypto_core_message());
DumpHex(&license_file, "core_response",
signed_response.oemcrypto_core_message());
}
video_widevine::License license;
EXPECT_TRUE(license.ParseFromString(signed_response.msg()));
PrintLicenseProto("License", license);
DumpHex(&license_file, "serialized_license", signed_response.msg());
std::string message =
signed_response.oemcrypto_core_message() + signed_response.msg();
if (wvoec::global_features.api_version >= wvoec::kCoreMessagesAPI) {
ODK_Message odk_msg = ODK_Message_Create(
reinterpret_cast<uint8_t*>(const_cast<char*>(message.c_str())),
message.length());
ODK_Message_SetSize(&odk_msg,
signed_response.oemcrypto_core_message().length());
ODK_ParsedLicense odk_parsed_license = {};
ODK_LicenseResponse odk_license_response = {};
odk_license_response.parsed_license = &odk_parsed_license;
Unpack_ODK_LicenseResponse(&odk_msg, &odk_license_response);
EXPECT_EQ(ODK_Message_GetStatus(&odk_msg), MESSAGE_STATUS_OK);
// Valid hash is only needed for v16 messages.
std::string hash(ODK_SHA256_HASH_SIZE, ' ');
DumpHex(&license_file, "core_request_sha256", hash);
license_file << " nonce_required_ = "
<< (odk_parsed_license.nonce_required ? "true" : "false")
<< ";\n";
}
license_file << " RunTest();\n";
license_file << "}\n\n";
}
void MessageDumper::DumpRenewalRequest(const CdmKeyRequest& request) {
DumpHeader(&renewal_file, "Renewal");
SignedMessage signed_message;
EXPECT_TRUE(signed_message.ParseFromString(request.message));
if (wvoec::global_features.api_version >= wvoec::kCoreMessagesAPI) {
EXPECT_TRUE(signed_message.has_oemcrypto_core_message());
DumpHex(&renewal_file, "core_request",
signed_message.oemcrypto_core_message());
}
video_widevine::LicenseRequest renewal_request;
EXPECT_TRUE(renewal_request.ParseFromString(signed_message.msg()));
PrintRequestProto("Renewal Request", renewal_request);
}
void MessageDumper::DumpRenewal(const std::string& response) {
SignedMessage signed_response;
EXPECT_TRUE(signed_response.ParseFromString(response))
<< "Response = " << wvutil::b2a_hex(response);
if (wvoec::global_features.api_version >= wvoec::kCoreMessagesAPI) {
EXPECT_TRUE(signed_response.has_oemcrypto_core_message());
DumpHex(&renewal_file, "core_response",
signed_response.oemcrypto_core_message());
}
video_widevine::License renewal;
EXPECT_TRUE(renewal.ParseFromString(signed_response.msg()));
PrintLicenseProto("Renewal", renewal);
DumpHex(&renewal_file, "renewal", signed_response.msg());
if (wvoec::global_features.api_version >= wvoec::kCoreMessagesAPI) {
std::string message =
signed_response.oemcrypto_core_message() + signed_response.msg();
ODK_Message odk_msg = ODK_Message_Create(
reinterpret_cast<uint8_t*>(const_cast<char*>(message.c_str())),
message.length());
ODK_Message_SetSize(&odk_msg,
signed_response.oemcrypto_core_message().length());
ODK_RenewalResponse odk_renewal_response = {};
Unpack_ODK_RenewalResponse(&odk_msg, &odk_renewal_response);
EXPECT_EQ(ODK_Message_GetStatus(&odk_msg), MESSAGE_STATUS_OK);
renewal_file << " renewal_duration_seconds_ = "
<< odk_renewal_response.renewal_duration_seconds << ";\n";
}
renewal_file << " RunTest();\n";
renewal_file << "}\n\n";
}
void MessageDumper::PrintProvisioningRequest(
const CdmProvisioningRequest& request) {
SignedProvisioningMessage signed_message;
ASSERT_TRUE(signed_message.ParseFromString(request));
if (wvoec::global_features.api_version >= wvoec::kCoreMessagesAPI) {
ASSERT_TRUE(signed_message.has_oemcrypto_core_message());
const std::string& core_request = signed_message.oemcrypto_core_message();
oemcrypto_core_message::ODK_ProvisioningRequest core_request_data;
ASSERT_TRUE(
oemcrypto_core_message::deserialize::CoreProvisioningRequestFromMessage(
core_request, &core_request_data));
// Print core message information for the provisioning request.
if (wvoec::global_features.derive_key_method ==
wvoec::DeviceFeatures::TEST_PROVISION_40) {
std::cout << std::endl
<< "Provisioning 4.0 Request Information:" << std::endl;
std::cout << " Provisioning 4.0 Request Core Version = "
<< core_request_data.api_major_version << "."
<< core_request_data.api_minor_version << std::endl;
} else {
std::cout << std::endl
<< "Provisioning Request Information:" << std::endl;
std::cout << " Provisioning Request Core Version = "
<< core_request_data.api_major_version << "."
<< core_request_data.api_minor_version << std::endl;
}
std::cout << " Nonce = " << core_request_data.nonce << std::endl;
std::cout << " Session ID = " << core_request_data.session_id << std::endl;
}
}
void MessageDumper::PrintProvisioningResponse(
const CdmProvisioningResponse& response) {
if (wvoec::global_features.derive_key_method ==
wvoec::DeviceFeatures::TEST_PROVISION_40) {
std::cout << " Provisioning 4.0 does not have a response. " << std::endl;
return;
}
SignedProvisioningMessage signed_response;
if (!signed_response.ParseFromString(response)) {
// A binary provisioning response is buried within a json structure.
std::string extracted_message;
ASSERT_TRUE(CertificateProvisioning::ExtractAndDecodeSignedMessage(
response, &extracted_message));
ASSERT_TRUE(signed_response.ParseFromString(extracted_message));
}
if (wvoec::global_features.api_version >= wvoec::kCoreMessagesAPI) {
ASSERT_TRUE(signed_response.has_oemcrypto_core_message());
std::string message =
signed_response.oemcrypto_core_message() + signed_response.message();
ODK_Message odk_msg = ODK_Message_Create(
reinterpret_cast<uint8_t*>(const_cast<char*>(message.c_str())),
message.length());
ODK_Message_SetSize(&odk_msg,
signed_response.oemcrypto_core_message().length());
ODK_ParsedProvisioning odk_parsed_provisioning;
// For v17 and earlier, we use a special v16/v17 provisioning response type.
// So need to separate the way we unpack the provisioning response into
// v16/v17 and v18+.
if (wvoec::global_features.api_version > 17) {
ODK_ProvisioningResponse odk_provisioning_response;
odk_provisioning_response.parsed_provisioning = &odk_parsed_provisioning;
Unpack_ODK_ProvisioningResponse(&odk_msg, &odk_provisioning_response);
EXPECT_EQ(ODK_Message_GetStatus(&odk_msg), MESSAGE_STATUS_OK);
// Print core message information for the provisioning response.
std::cout << std::endl
<< "Provisioning Response Information:" << std::endl;
std::cout << " Provisioning Response Core Version = "
<< odk_provisioning_response.core_message.nonce_values
.api_major_version
<< "."
<< odk_provisioning_response.core_message.nonce_values
.api_minor_version
<< std::endl;
std::cout << " Nonce = "
<< odk_provisioning_response.core_message.nonce_values.nonce
<< std::endl;
std::cout
<< " Session ID = "
<< odk_provisioning_response.core_message.nonce_values.session_id
<< std::endl;
std::cout << " Key Type = "
<< ((odk_parsed_provisioning.key_type ==
OEMCrypto_RSA_Private_Key)
? "OEMCrypto_RSA_Private_Key"
: "OEMCrypto_ECC_Private_Key")
<< std::endl;
} else {
// ODK_ParsedProvisioning odk_parsed_provisioning;
ODK_ProvisioningResponseV16 odk_provisioning_response;
odk_provisioning_response.parsed_provisioning = &odk_parsed_provisioning;
Unpack_ODK_ProvisioningResponseV16(&odk_msg, &odk_provisioning_response);
EXPECT_EQ(ODK_Message_GetStatus(&odk_msg), MESSAGE_STATUS_OK);
// Print core message information for the provisioning response.
std::cout << std::endl
<< "Provisioning Response Information:" << std::endl;
std::cout << " Provisioning Response Core Version = "
<< odk_provisioning_response.request.core_message.nonce_values
.api_major_version
<< "."
<< odk_provisioning_response.request.core_message.nonce_values
.api_minor_version
<< std::endl;
std::cout
<< " Nonce = "
<< odk_provisioning_response.request.core_message.nonce_values.nonce
<< std::endl;
std::cout << " Session ID = "
<< odk_provisioning_response.request.core_message.nonce_values
.session_id
<< std::endl;
std::cout << " Key Type = "
<< ((odk_parsed_provisioning.key_type ==
OEMCrypto_RSA_Private_Key)
? "OEMCrypto_RSA_Private_Key"
: "OEMCrypto_ECC_Private_Key")
<< std::endl;
}
}
}
void MessageDumper::DumpProvisioningRequest(
const CdmProvisioningRequest& request) {
if (wvoec::global_features.derive_key_method ==
wvoec::DeviceFeatures::TEST_PROVISION_40) {
// The ODKGoldenProvision40V19 test will have its own class for now since
// we are only testing the request.
DumpHeader(&provision_file, "Provision40");
} else {
DumpHeader(&provision_file, "Provision");
}
SignedProvisioningMessage signed_message;
EXPECT_TRUE(signed_message.ParseFromString(request))
<< "Request = " << wvutil::b2a_hex(request);
if (wvoec::global_features.api_version >= wvoec::kCoreMessagesAPI) {
EXPECT_TRUE(signed_message.has_oemcrypto_core_message());
DumpHex(&provision_file, "core_request",
signed_message.oemcrypto_core_message());
}
}
void MessageDumper::DumpProvisioning(const CdmProvisioningResponse& response) {
if (wvoec::global_features.derive_key_method ==
wvoec::DeviceFeatures::TEST_PROVISION_40) {
LOGD(
"Provisioning 4.0 does not have a v17, v18 or v19 core message in "
"the "
"response.");
provision_file << " RunTest();\n";
provision_file << "}\n\n";
} else {
SignedProvisioningMessage signed_response;
if (!signed_response.ParseFromString(response)) {
// A binary provisioning response is buried within a json structure.
std::string extracted_message;
EXPECT_TRUE(CertificateProvisioning::ExtractAndDecodeSignedMessage(
response, &extracted_message));
EXPECT_TRUE(signed_response.ParseFromString(extracted_message));
}
if (wvoec::global_features.api_version >= wvoec::kCoreMessagesAPI) {
EXPECT_TRUE(signed_response.has_oemcrypto_core_message());
DumpHex(&provision_file, "core_response",
signed_response.oemcrypto_core_message());
}
DumpHex(&provision_file, "provisioning_response",
signed_response.message());
// The choice of ECC or RSA key is decided at the server, based on
// information in the DCSL. We can only reproduce this by looking
// at the current response.
std::string message =
signed_response.oemcrypto_core_message() + signed_response.message();
ODK_Message odk_msg = ODK_Message_Create(
reinterpret_cast<uint8_t*>(const_cast<char*>(message.c_str())),
message.length());
ODK_Message_SetSize(&odk_msg,
signed_response.oemcrypto_core_message().length());
ODK_ParsedProvisioning odk_parsed_provisioning;
ODK_ProvisioningResponse odk_provisioning_response;
odk_provisioning_response.parsed_provisioning = &odk_parsed_provisioning;
Unpack_ODK_ProvisioningResponse(&odk_msg, &odk_provisioning_response);
EXPECT_EQ(ODK_Message_GetStatus(&odk_msg), MESSAGE_STATUS_OK);
provision_file << " device_key_type_ = "
<< ((odk_parsed_provisioning.key_type ==
OEMCrypto_RSA_Private_Key)
? "OEMCrypto_RSA_Private_Key;\n"
: "OEMCrypto_ECC_Private_Key;\n");
provision_file << " RunTest();\n";
provision_file << "}\n\n";
}
}
} // namespace wvcdm