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:
Fred Gylys-Colwell
2022-12-15 23:04:14 -08:00
committed by Robert Shih
parent cebd90e300
commit e9b0196a23
10 changed files with 306 additions and 11 deletions

View File

@@ -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.

View File

@@ -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);

View File

@@ -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();
}

View 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

View 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_

View File

@@ -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

View File

@@ -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,

View File

@@ -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_;

View File

@@ -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) {

View File

@@ -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 \