From e9b0196a23c6ca67d09552a5e657e88cf0a8165c Mon Sep 17 00:00:00 2001 From: Fred Gylys-Colwell Date: Thu, 15 Dec 2022 23:04:14 -0800 Subject: [PATCH] Generate golden data tests for ODK Generate core message request and responses for golden data tests. This CL does not have any golden data. The golden data will be added to a google3 CL. To turn on dumping of golden data, set the environment variable DUMP_GOLDEN_DATA to "yes". Merged from https://widevine-internal-review.googlesource.com/171750 Change-Id: I7ae2d76ec7330d9131aae98dfd07b7909d10f726 --- .../cdm/core/test/config_test_env.h | 8 + .../cdm/core/test/keybox_ota_test.cpp | 3 +- .../cdm/core/test/license_holder.cpp | 13 ++ .../cdm/core/test/message_dumper.cpp | 218 ++++++++++++++++++ libwvdrmengine/cdm/core/test/message_dumper.h | 39 ++++ .../cdm/core/test/policy_integration_test.cpp | 3 +- .../cdm/core/test/provisioning_holder.cpp | 10 + .../cdm/core/test/provisioning_holder.h | 11 +- libwvdrmengine/cdm/core/test/test_base.cpp | 11 +- libwvdrmengine/cdm/test/integration-test.mk | 1 + 10 files changed, 306 insertions(+), 11 deletions(-) create mode 100644 libwvdrmengine/cdm/core/test/message_dumper.cpp create mode 100644 libwvdrmengine/cdm/core/test/message_dumper.h diff --git a/libwvdrmengine/cdm/core/test/config_test_env.h b/libwvdrmengine/cdm/core/test/config_test_env.h index bab2b3c9..9b3906b4 100644 --- a/libwvdrmengine/cdm/core/test/config_test_env.h +++ b/libwvdrmengine/cdm/core/test/config_test_env.h @@ -134,6 +134,11 @@ class ConfigTestEnv { // The QA service certificate, used for a local provisioning server. static std::string QAProvisioningServiceCertificate(); + bool dump_golden_data() const { return dump_golden_data_; } + void set_dump_golden_data(bool dump_golden_data) { + dump_golden_data_ = dump_golden_data; + } + private: void Init(ServerConfigurationId server_id); @@ -149,6 +154,9 @@ class ConfigTestEnv { int test_pass_; std::string test_data_path_; // Where to store test data for reboot tests. int server_version_ = 0; + // It dump_golden_data_ is true, message data is dumped to a file for help + // in generating golden test data. + bool dump_golden_data_ = false; }; // The default provisioning server URL for a default provisioning request. diff --git a/libwvdrmengine/cdm/core/test/keybox_ota_test.cpp b/libwvdrmengine/cdm/core/test/keybox_ota_test.cpp index fc5a2df9..efffd785 100644 --- a/libwvdrmengine/cdm/core/test/keybox_ota_test.cpp +++ b/libwvdrmengine/cdm/core/test/keybox_ota_test.cpp @@ -44,8 +44,7 @@ class CdmOtaKeyboxTest : public ::testing::Test { void Provision(TestCdmEngine* cdm_engine) { ConfigTestEnv config = *WvCdmTestBase::default_config_; - ProvisioningHolder provisioner(cdm_engine, config.provisioning_server(), - config.provisioning_service_certificate()); + ProvisioningHolder provisioner(cdm_engine, config); CdmCertificateType cert_type = kCertificateWidevine; constexpr bool binary_provisioning = false; provisioner.Provision(cert_type, binary_provisioning); diff --git a/libwvdrmengine/cdm/core/test/license_holder.cpp b/libwvdrmengine/cdm/core/test/license_holder.cpp index 873fe0f9..ceef5315 100644 --- a/libwvdrmengine/cdm/core/test/license_holder.cpp +++ b/libwvdrmengine/cdm/core/test/license_holder.cpp @@ -5,6 +5,7 @@ #include "license_holder.h" #include "license_request.h" +#include "message_dumper.h" #include "oec_device_features.h" #include "test_base.h" @@ -37,8 +38,14 @@ void LicenseHolder::FetchLicense() { CdmKeyRequest key_request; ASSERT_NO_FATAL_FAILURE(GenerateKeyRequest(init_data, &key_request)) << "Failed for " << content_id(); + if (config_.dump_golden_data()) { + MessageDumper::DumpLicenseRequest(key_request); + } ASSERT_NO_FATAL_FAILURE(GetKeyResponse(key_request)) << "Failed for " << content_id(); + if (config_.dump_golden_data()) { + MessageDumper::DumpLicense(key_response_); + } } void LicenseHolder::LoadLicense() { @@ -75,6 +82,9 @@ void LicenseHolder::GenerateAndPostRenewalRequest( const CdmResponseType result = cdm_engine_->GenerateRenewalRequest(session_id_, &request); ASSERT_EQ(KEY_MESSAGE, result) << "Failed for " << content_id(); + if (config_.dump_golden_data()) { + MessageDumper::DumpRenewalRequest(request); + } const std::string url = MakeUrl(config_.renewal_server(), policy_id); renewal_in_flight_.reset(new UrlRequest(url)); ASSERT_TRUE(renewal_in_flight_->is_connected()) @@ -91,6 +101,9 @@ void LicenseHolder::FetchRenewal() { void LicenseHolder::LoadRenewal() { LicenseRequest license_request; license_request.GetDrmMessage(renewal_response_, renewal_message_); + if (config_.dump_golden_data()) { + MessageDumper::DumpRenewal(renewal_message_); + } EXPECT_EQ(KEY_ADDED, cdm_engine_->RenewKey(session_id_, renewal_message_)) << "Failed for " << content_id(); } diff --git a/libwvdrmengine/cdm/core/test/message_dumper.cpp b/libwvdrmengine/cdm/core/test/message_dumper.cpp new file mode 100644 index 00000000..75ad7999 --- /dev/null +++ b/libwvdrmengine/cdm/core/test/message_dumper.cpp @@ -0,0 +1,218 @@ +// 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 "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) { + *out << "const uint8_t " << name << "_raw[] = {\n"; + *out << " "; + for (unsigned int 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(" << name << "_raw), \n" + << " sizeof(" << name << "_raw));\n"; + *out << std::dec; // Turn off hex when we're done. +} +} // 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::DumpLicenseRequest(const CdmKeyRequest& request) { + SignedMessage signed_message; + DumpHeader(&license_file, "License"); + EXPECT_TRUE(signed_message.ParseFromString(request.message)); + 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())); +} + +void MessageDumper::DumpLicense(const std::string& response) { + SignedMessage signed_response; + EXPECT_TRUE(signed_response.ParseFromString(response)); + 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())); + DumpHex(&license_file, "serialized_license", signed_response.msg()); + + std::string message = + signed_response.oemcrypto_core_message() + signed_response.msg(); + ODK_Message odk_msg = ODK_Message_Create( + reinterpret_cast(const_cast(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)); + 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())); +} + +void MessageDumper::DumpRenewal(const std::string& response) { + SignedMessage signed_response; + EXPECT_TRUE(signed_response.ParseFromString(response)) + << "Response = " << wvutil::b2a_hex(response); + 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())); + DumpHex(&renewal_file, "renewal", signed_response.msg()); + + std::string message = + signed_response.oemcrypto_core_message() + signed_response.msg(); + ODK_Message odk_msg = ODK_Message_Create( + reinterpret_cast(const_cast(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::DumpProvisioningRequest( + const CdmProvisioningRequest& request) { + if (wvoec::global_features.derive_key_method == + wvoec::DeviceFeatures::TEST_PROVISION_40) { + LOGD("Provisioning 4.0 does not have a v17 or v18 core message."); + } else { + DumpHeader(&provision_file, "Provision"); + SignedProvisioningMessage signed_message; + EXPECT_TRUE(signed_message.ParseFromString(request)) + << "Request = " << wvutil::b2a_hex(request); + 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 core message."); + } 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)); + } + 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(const_cast(message.c_str())), + message.length()); + ODK_Message_SetSize(&odk_msg, + signed_response.oemcrypto_core_message().length()); + ODK_ParsedProvisioning odk_parsed_response; + ODK_ProvisioningResponse provisioning_response; + provisioning_response.parsed_provisioning = &odk_parsed_response; + Unpack_ODK_ProvisioningResponse(&odk_msg, &provisioning_response); + EXPECT_EQ(ODK_Message_GetStatus(&odk_msg), MESSAGE_STATUS_OK); + provision_file << " device_key_type_ = " + << ((odk_parsed_response.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 diff --git a/libwvdrmengine/cdm/core/test/message_dumper.h b/libwvdrmengine/cdm/core/test/message_dumper.h new file mode 100644 index 00000000..61843d2a --- /dev/null +++ b/libwvdrmengine/cdm/core/test/message_dumper.h @@ -0,0 +1,39 @@ +// Copyright 2023 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. + +#ifndef WVCDM_CORE_TEST_MESSAGE_DUMPER_H_ +#define WVCDM_CORE_TEST_MESSAGE_DUMPER_H_ + +#include +#include +#include + +#include + +#include "log.h" +#include "wv_cdm_types.h" + +namespace wvcdm { + +class MessageDumper : public ::testing::Environment { + // This dumps messages to a file so that the data can easily be turned + // into golden data tests for the ODK library. + public: + ~MessageDumper() override {} + void SetUp() override; + void TearDown() override; + + static void DumpLicenseRequest(const CdmKeyRequest& request); + static void DumpLicense(const std::string& response); + static void DumpRenewalRequest(const CdmKeyRequest& request); + static void DumpRenewal(const std::string& response); + static void DumpProvisioningRequest(const CdmProvisioningRequest& request); + static void DumpProvisioning(const CdmProvisioningResponse& response); + static std::ofstream license_file; + static std::ofstream renewal_file; + static std::ofstream provision_file; +}; +} // namespace wvcdm + +#endif // WVCDM_CORE_TEST_MESSAGE_DUMPER_H_ diff --git a/libwvdrmengine/cdm/core/test/policy_integration_test.cpp b/libwvdrmengine/cdm/core/test/policy_integration_test.cpp index 94730cb8..2f6f8d1c 100644 --- a/libwvdrmengine/cdm/core/test/policy_integration_test.cpp +++ b/libwvdrmengine/cdm/core/test/policy_integration_test.cpp @@ -127,8 +127,7 @@ TEST_F(CorePIGTest, CastReceiverProvisioningUsingCdm) { } // Provision x509 cert for CAST Receiver. - ProvisioningHolder provisioner(&cdm_engine_, config_.provisioning_server(), - config_.provisioning_service_certificate()); + ProvisioningHolder provisioner(&cdm_engine_, config_); provisioner.Provision(kCertificateX509, binary_provisioning_); // cdm_engine_.SignRsa diff --git a/libwvdrmengine/cdm/core/test/provisioning_holder.cpp b/libwvdrmengine/cdm/core/test/provisioning_holder.cpp index 8cd7fa81..954b2eff 100644 --- a/libwvdrmengine/cdm/core/test/provisioning_holder.cpp +++ b/libwvdrmengine/cdm/core/test/provisioning_holder.cpp @@ -10,6 +10,7 @@ #include "config_test_env.h" #include "log.h" +#include "message_dumper.h" #include "oec_device_features.h" #include "test_printers.h" #include "test_sleep.h" @@ -47,6 +48,12 @@ void ProvisioningHolder::Provision(CdmCertificateType cert_type, if (binary_provisioning) { request = wvutil::Base64SafeEncodeNoPad(request); } + if (config_.dump_golden_data()) { + std::vector binary_request = wvutil::Base64SafeDecode(request); + CdmProvisioningRequest binary_request_string(binary_request.begin(), + binary_request.end()); + MessageDumper::DumpProvisioningRequest(binary_request_string); + } LOGV("Provisioning request: req = %s", request.c_str()); // Ignore URL provided by CdmEngine. Use ours, as configured @@ -95,6 +102,9 @@ void ProvisioningHolder::Provision(CdmCertificateType cert_type, << (binary_provisioning ? "Binary provisioning failed. " : "Non-binary provisioning failed. ") << DumpProvAttempt(request, response_, cert_type); + if (config_.dump_golden_data()) { + MessageDumper::DumpProvisioning(response_); + } } bool ProvisioningHolder::ExtractSignedMessage(const std::string& response, diff --git a/libwvdrmengine/cdm/core/test/provisioning_holder.h b/libwvdrmengine/cdm/core/test/provisioning_holder.h index fe5acd2d..1b68c9cc 100644 --- a/libwvdrmengine/cdm/core/test/provisioning_holder.h +++ b/libwvdrmengine/cdm/core/test/provisioning_holder.h @@ -13,12 +13,12 @@ namespace wvcdm { class ProvisioningHolder { public: - ProvisioningHolder(TestCdmEngine* cdm_engine, - const std::string& provisioning_server_url, - const std::string& provisioning_service_certificate) + ProvisioningHolder(TestCdmEngine* cdm_engine, const ConfigTestEnv& config) : cdm_engine_(cdm_engine), - provisioning_server_url_(provisioning_server_url), - provisioning_service_certificate_(provisioning_service_certificate) {} + config_(config), + provisioning_server_url_(config.provisioning_server()), + provisioning_service_certificate_( + config.provisioning_service_certificate()) {} void Provision(CdmCertificateType cert_type, bool binary_provisioning); void Provision(bool binary_provisioning) { Provision(kCertificateWidevine, binary_provisioning); @@ -29,6 +29,7 @@ class ProvisioningHolder { protected: TestCdmEngine* cdm_engine_; + const ConfigTestEnv& config_; std::string provisioning_server_url_; std::string provisioning_service_certificate_; std::string response_; diff --git a/libwvdrmengine/cdm/core/test/test_base.cpp b/libwvdrmengine/cdm/core/test/test_base.cpp index e54a64dc..59ab761e 100644 --- a/libwvdrmengine/cdm/core/test/test_base.cpp +++ b/libwvdrmengine/cdm/core/test/test_base.cpp @@ -22,6 +22,7 @@ #include "file_store.h" #include "license.h" #include "log.h" +#include "message_dumper.h" #include "oec_device_features.h" #include "oec_test_data.h" #include "platform.h" @@ -142,6 +143,10 @@ void show_menu(const char* prog_name, const std::string& extra_help_text) { std::cout << " --test_data_path=" << std::endl; std::cout << " Where to store test data for reboot tests." << std::endl; + std::cout << " --dump_golden_data" << std::endl; + std::cout << " Dump the license request and response from the server." + << std::endl; + std::cout << extra_help_text << std::endl; } } // namespace @@ -319,8 +324,7 @@ void WvCdmTestBase::Provision() { std::unique_ptr file_system(CreateTestFileSystem()); TestCdmEngine cdm_engine(file_system.get(), std::make_shared()); - ProvisioningHolder provisioner(&cdm_engine, config_.provisioning_server(), - config_.provisioning_service_certificate()); + ProvisioningHolder provisioner(&cdm_engine, config_); provisioner.Provision(cert_type, binary_provisioning_); } @@ -391,6 +395,9 @@ bool WvCdmTestBase::Initialize(int argc, const char* const argv[], } else if (arg.find("--gtest") == 0) { // gtest arguments will be passed to gtest by the main program. continue; + } else if (arg == "--dump_golden_data") { + default_config_->set_dump_golden_data(true); + testing::AddGlobalTestEnvironment(new MessageDumper); } else { const auto index = arg.find('='); if (index == std::string::npos) { diff --git a/libwvdrmengine/cdm/test/integration-test.mk b/libwvdrmengine/cdm/test/integration-test.mk index 16acbd0c..00f5140a 100644 --- a/libwvdrmengine/cdm/test/integration-test.mk +++ b/libwvdrmengine/cdm/test/integration-test.mk @@ -20,6 +20,7 @@ LOCAL_SRC_FILES := \ ../core/test/http_socket.cpp \ ../core/test/license_holder.cpp \ ../core/test/license_request.cpp \ + ../core/test/message_dumper.cpp \ ../core/test/provisioning_holder.cpp \ ../core/test/test_base.cpp \ ../core/test/test_printers.cpp \