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
This commit is contained in:
committed by
Robert Shih
parent
cebd90e300
commit
e9b0196a23
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
218
libwvdrmengine/cdm/core/test/message_dumper.cpp
Normal file
218
libwvdrmengine/cdm/core/test/message_dumper.cpp
Normal file
@@ -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<const char *>(" << 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<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));
|
||||
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<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::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<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_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
|
||||
39
libwvdrmengine/cdm/core/test/message_dumper.h
Normal file
39
libwvdrmengine/cdm/core/test/message_dumper.h
Normal file
@@ -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 <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#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_
|
||||
@@ -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
|
||||
|
||||
@@ -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<uint8_t> 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,
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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=<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<wvutil::FileSystem> file_system(CreateTestFileSystem());
|
||||
TestCdmEngine cdm_engine(file_system.get(),
|
||||
std::make_shared<EngineMetrics>());
|
||||
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) {
|
||||
|
||||
@@ -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 \
|
||||
|
||||
Reference in New Issue
Block a user