Files
android/libwvdrmengine/cdm/core/test/test_base.cpp
Rahul Frias fd00a8af24 Remove old test license holder
[ Merged from http://go/wvgerrit/143750 ]

The old test license holder would generate a minimal license response,
but could not correctly mimic important server logic introduced in the
v16 server. Since all integration tests now have policies on the UAT
server, we do not need these minimalist license responses anymore.

Bug: 192700112
Test: GtsMediaTestCases on sunfish
Change-Id: I78c1b6085a6d0239840a11f2b904902210e5e61c
2022-03-16 01:36:38 -07:00

602 lines
24 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 "test_base.h"
#include <openssl/aes.h>
#include <openssl/bio.h>
#include <openssl/cmac.h>
#include <stdlib.h>
#include <chrono>
#include <string>
#include <thread>
#include <vector>
#include "cdm_engine.h"
#include "clock.h"
#include "crypto_session.h"
#include "fake_provisioning_server.h"
#include "file_store.h"
#include "license.h"
#include "log.h"
#include "oec_device_features.h"
#include "oec_test_data.h"
#include "platform.h"
#include "properties.h"
#include "test_printers.h"
#include "test_sleep.h"
#include "url_request.h"
using wvcdm::metrics::EngineMetrics;
namespace wvcdm {
namespace {
void show_menu(const char* prog_name, const std::string& extra_help_text) {
std::cout << std::endl;
std::cout << "usage: " << prog_name << " [options]" << std::endl << std::endl;
std::cout << " enclose multiple arguments in '' when using adb shell"
<< std::endl;
std::cout << " e.g. adb shell '" << prog_name << " --server=\"url\"'"
<< std::endl;
std::cout << " --verbose or -v" << std::endl;
std::cout << " increase logging verbosity (may be repeated)" << std::endl
<< std::endl;
std::cout << " --no_filter" << std::endl;
std::cout << " Do not filter out inappropriate tests" << std::endl
<< std::endl;
std::cout << " --cast" << std::endl;
std::cout << " Run tests appropriate for a Cast Receiver" << std::endl
<< std::endl;
std::cout << " --license_server_id=<gp/cp/st>" << std::endl;
std::cout << " specifies which default server settings to use: "
<< std::endl;
std::cout << " gp for GooglePlay server" << std::endl;
std::cout << " cp for Content Protection UAT server" << std::endl;
std::cout << " st for Content Protection Staging server" << std::endl
<< std::endl;
std::cout << " --keyid=<key_id>" << std::endl;
std::cout << " configure the key id or pssh, in hex format" << std::endl
<< std::endl;
std::cout << " --service_certificate=<cert>" << std::endl;
std::cout << " configure the signed license service certificate"
<< std::endl;
std::cout << " Specify the SignedDeviceCertificate (from "
<< "device_certificate.proto) " << std::endl;
std::cout << " in hex format." << std::endl;
std::cout << " Due to the length of the argument use, " << std::endl;
std::cout << " echo \"/system/bin/request_license_test -s \\\""
<< "0ABF02...A29914\\\"\" \\" << std::endl;
std::cout << " > run_request_license_test.sh" << std::endl;
std::cout << " chmod +x run_request_license_test.sh" << std::endl;
std::cout << " adb push run_request_license_test.sh /system/bin"
<< std::endl;
std::cout << " adb shell sh /system/bin/run_request_license_test.sh"
<< std::endl
<< std::endl;
std::cout << " --provisioning_certificate=<cert>" << std::endl;
std::cout << " configure the signed provisioning service certificate"
<< std::endl
<< " in hex" << std::endl
<< std::endl;
std::cout << " --license_server_url=<url>" << std::endl;
std::cout << " configure the license server url, please include http[s]"
<< " in the url" << std::endl
<< std::endl;
std::cout << " --renewal_server_url=<url>" << std::endl;
std::cout << " configure the renewal server url, please include http[s] "
"in the url"
<< std::endl
<< " If not set, this defaults to be the same url used by the "
"license server."
<< std::endl
<< " Some tests, such as LicenseRenewalSpecifiedServer, will "
"ignore this setting. "
<< std::endl
<< " See comments in the code for an explanation." << std::endl
<< std::endl;
std::cout << " --provisioning_server_url=<url>" << std::endl;
std::cout
<< " configure the provisioning server url, please include http[s]"
<< " in the url" << std::endl
<< std::endl;
std::cout << " --qa_provisioning" << std::endl;
std::cout << " use the QA provisioning cert and QA test keybox"
<< std::endl
<< std::endl;
std::cout << " --fake_sleep" << std::endl;
std::cout << " Use a fake clock to sleep for duration tests. This cannot"
<< " be used with a real OEMCrypto." << std::endl
<< std::endl;
std::cout << " --initial_time=<time>" << std::endl;
std::cout
<< " Set the initial time on the fake clock. This is ignored"
<< " if fake_sleep was not set." << std::endl
<< std::endl;
std::cout << " --pass=<N>" << std::endl;
std::cout << " Run test pass N. This is used for reboot tests that "
<< "require several passes." << std::endl
<< std::endl;
std::cout << " --test_data_path=<path>" << std::endl;
std::cout << " Where to store test data for reboot tests." << std::endl;
std::cout << extra_help_text << std::endl;
}
/*
* Locate the portion of the server's response message that is between
* the strings jason_start_substr and json_end_substr. Returns the string
* through *result. If the start substring match fails, assume the entire
* string represents a serialized protobuf mesaage and return true with
* the entire string. If the end_substring match fails, return false with
* an empty *result.
*/
bool ExtractSignedMessage(const std::string& response,
const std::string& json_start_substr,
const std::string& json_end_substr,
std::string* result) {
std::string response_string;
size_t start = response.find(json_start_substr);
if (start == response.npos) {
// Assume serialized protobuf message.
result->assign(response);
} else {
// Assume JSON-wrapped protobuf.
size_t end =
response.find(json_end_substr, start + json_start_substr.length());
if (end == response.npos) {
LOGE("ExtractSignedMessage cannot locate end substring");
result->clear();
return false;
}
size_t result_string_size = end - start - json_start_substr.length();
result->assign(response, start + json_start_substr.length(),
result_string_size);
}
if (result->empty()) {
LOGE("ExtractSignedMessage: Response message is empty");
return false;
}
return true;
}
} // namespace
ConfigTestEnv WvCdmTestBase::default_config_(kContentProtectionUatServer);
bool WvCdmTestBase::use_qa_test_keybox_ = false;
void WvCdmTestBase::StripeBuffer(std::vector<uint8_t>* buffer, size_t size,
uint8_t init) {
buffer->assign(size, 0);
for (size_t i = 0; i < size; i++) {
(*buffer)[i] = init + i % 250;
}
}
std::string WvCdmTestBase::Aes128CbcEncrypt(std::vector<uint8_t> key,
const std::vector<uint8_t>& clear,
const std::vector<uint8_t> iv) {
std::vector<uint8_t> encrypted(clear.size());
std::vector<uint8_t> iv_mod(iv.begin(), iv.end());
AES_KEY aes_key;
AES_set_encrypt_key(&key[0], 128, &aes_key);
AES_cbc_encrypt(&clear[0], &encrypted[0], clear.size(), &aes_key, &iv_mod[0],
AES_ENCRYPT);
return std::string(encrypted.begin(), encrypted.end());
}
std::string WvCdmTestBase::Aes128CbcDecrypt(std::vector<uint8_t> key,
const std::vector<uint8_t>& clear,
const std::vector<uint8_t> iv) {
std::vector<uint8_t> encrypted(clear.size());
std::vector<uint8_t> iv_mod(iv.begin(), iv.end());
AES_KEY aes_key;
AES_set_decrypt_key(&key[0], 128, &aes_key);
AES_cbc_encrypt(&clear[0], &encrypted[0], clear.size(), &aes_key, &iv_mod[0],
AES_DECRYPT);
return std::string(encrypted.begin(), encrypted.end());
}
std::string WvCdmTestBase::SignHMAC(const std::string& message,
const std::vector<uint8_t>& key) {
uint8_t signature[SHA256_DIGEST_LENGTH];
unsigned int md_len = SHA256_DIGEST_LENGTH;
HMAC(EVP_sha256(), &key[0], static_cast<int>(key.size()),
reinterpret_cast<const uint8_t*>(message.data()), message.size(),
signature, &md_len);
std::string result(signature, signature + SHA256_DIGEST_LENGTH);
return result;
}
TestCryptoSession::TestCryptoSession(metrics::CryptoMetrics* crypto_metrics)
: CryptoSession(crypto_metrics) {
// The first CryptoSession should have initialized OEMCrypto. This is right
// after that, so we should tell oemcrypto to use a test keybox.
if (session_count() == 1) {
CryptoSession::SetAllowTestKeybox(true);
ReinitializeForTest();
WvCdmTestBase::InstallTestRootOfTrust();
}
}
CdmResponseType TestCryptoSession::GenerateNonce(uint32_t* nonce) {
CdmResponseType status = CryptoSession::GenerateNonce(nonce);
for (int i = 0; status != NO_ERROR; i++) {
LOGV("Recovering from nonce flood.");
if (i > 2) return status;
wvutil::TestSleep::Sleep(1);
status = CryptoSession::GenerateNonce(nonce);
}
return NO_ERROR;
}
class TestCryptoSessionFactory : public CryptoSessionFactory {
CryptoSession* MakeCryptoSession(
metrics::CryptoMetrics* crypto_metrics) override {
// We need to add extra locking here because we need to make sure that there
// are no other OEMCrypto calls between OEMCrypto_Initialize and
// InstallTestRootOfTrust. OEMCrypto_Initialize is called in the production
// CryptoSession::Init and is wrapped in crypto_lock_, but
// InstallTestRootOfTrust is only called in the constructor of the
// TestCryptoSession, above.
std::unique_lock<std::mutex> auto_lock(init_lock_);
return new TestCryptoSession(crypto_metrics);
}
std::mutex init_lock_;
};
void WvCdmTestBase::SetUp() {
::testing::Test::SetUp();
Properties::Init();
Properties::set_provisioning_messages_are_binary(binary_provisioning_);
// Log the current test name, to help with debugging when the log and stdout
// are not the same.
const ::testing::TestInfo* const test_info =
::testing::UnitTest::GetInstance()->current_test_info();
LOGD("Running test %s.%s", test_info->test_case_name(), test_info->name());
// Some test environments allow the model name of the device to be set
// dynamically using an environment variable. The model name will show up in
// the license server logs as the part of the device idenfication as
// "model_name".
std::string model_name =
std::string(test_info->test_case_name()) + "." + test_info->name();
int overwrite = 1; // Set value even if already set.
setenv("MODEL_NAME", model_name.c_str(), overwrite);
CryptoSession::SetCryptoSessionFactory(new TestCryptoSessionFactory());
// TODO(fredgc): Add a test version of DeviceFiles.
}
void WvCdmTestBase::InstallTestRootOfTrust() {
const wvoec::WidevineKeybox& test_keybox =
use_qa_test_keybox_ ? wvoec::kQATestKeybox : wvoec::kTestKeybox;
switch (wvoec::global_features.derive_key_method) {
case wvoec::DeviceFeatures::LOAD_TEST_KEYBOX:
ASSERT_EQ(OEMCrypto_SUCCESS,
OEMCrypto_LoadTestKeybox(
reinterpret_cast<const uint8_t*>(&test_keybox),
sizeof(test_keybox)));
break;
case wvoec::DeviceFeatures::LOAD_TEST_RSA_KEY:
// Rare case: used by devices with baked in DRM cert.
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_LoadTestRSAKey());
break;
case wvoec::DeviceFeatures::TEST_PROVISION_30:
// Can use oem certificate to install test rsa key.
break;
case wvoec::DeviceFeatures::TEST_PROVISION_40:
// OEM certificate is retrieved from the server.
break;
default:
FAIL() << "Cannot run test without test keybox or RSA key installed.";
}
}
WvCdmTestBase::WvCdmTestBase()
: config_(default_config_), binary_provisioning_(false) {
const ::testing::TestInfo* const test_info =
::testing::UnitTest::GetInstance()->current_test_info();
LOGD("Running test %s.%s", test_info->test_case_name(), test_info->name());
}
void WvCdmTestBase::Provision() {
CdmProvisioningRequest prov_request;
std::string provisioning_server_url;
CdmCertificateType cert_type = kCertificateWidevine;
std::string cert_authority;
std::string cert, wrapped_key;
CdmSessionId session_id;
wvutil::FileSystem file_system;
if (config_.provisioning_server() == "fake") {
LOGD("Using fake provisioning server.");
TestCdmEngine cdm_engine(&file_system,
std::shared_ptr<EngineMetrics>(new EngineMetrics));
FakeProvisioningServer server;
CdmResponseType result = cdm_engine.GetProvisioningRequest(
cert_type, cert_authority, server.service_certificate(), kLevelDefault,
&prov_request, &provisioning_server_url);
ASSERT_EQ(NO_ERROR, result);
if (!binary_provisioning_) {
std::vector<uint8_t> prov_request_v =
wvutil::Base64SafeDecode(prov_request);
prov_request = std::string(prov_request_v.begin(), prov_request_v.end());
}
std::string response;
ASSERT_TRUE(server.MakeResponse(prov_request, &response))
<< "Fake provisioning server could not provision";
result = cdm_engine.HandleProvisioningResponse(response, kLevelDefault,
&cert, &wrapped_key);
EXPECT_EQ(NO_ERROR, result);
} else {
// TODO(fredgc): provision for different SPOIDs.
TestCdmEngine cdm_engine(&file_system,
std::shared_ptr<EngineMetrics>(new EngineMetrics));
CdmResponseType result = cdm_engine.GetProvisioningRequest(
cert_type, cert_authority, config_.provisioning_service_certificate(),
kLevelDefault, &prov_request, &provisioning_server_url);
ASSERT_EQ(NO_ERROR, result);
if (binary_provisioning_) {
prov_request = wvutil::Base64SafeEncodeNoPad(prov_request);
}
LOGV("Provisioning request: req = %s", prov_request.c_str());
// Ignore URL provided by CdmEngine. Use ours, as configured
// for test vs. production server.
provisioning_server_url.assign(config_.provisioning_server());
// Make request.
UrlRequest url_request(provisioning_server_url);
if (!url_request.is_connected()) {
LOGE("Failed to connect to provisioning server: url = %s",
provisioning_server_url.c_str());
}
url_request.PostCertRequestInQueryString(prov_request);
// Receive and parse response.
std::string http_message;
ASSERT_TRUE(url_request.GetResponse(&http_message))
<< "Failed to get provisioning response";
LOGV("http_message: \n%s\n", http_message.c_str());
if (binary_provisioning_) {
// extract provisioning response from received message
// Extracts signed response from JSON string, result is serialized
// protobuf.
static const std::string kMessageStart = "\"signedResponse\": \"";
static const std::string kMessageEnd = "\"";
std::string protobuf_response;
const bool extract_ok = ExtractSignedMessage(
http_message, kMessageStart, kMessageEnd, &protobuf_response);
ASSERT_TRUE(extract_ok) << "Failed to extract signed serialized "
"response from JSON response";
LOGV("Extracted response message: \n%s\n", protobuf_response.c_str());
ASSERT_FALSE(protobuf_response.empty())
<< "Protobuf response is unexpectedly empty";
// base64 decode response to yield binary protobuf
const std::vector<uint8_t> response_vec(
wvutil::Base64SafeDecode(protobuf_response));
ASSERT_FALSE(response_vec.empty())
<< "Failed to decode base64 of response: response = "
<< protobuf_response;
const std::string binary_protobuf_response(response_vec.begin(),
response_vec.end());
ASSERT_EQ(NO_ERROR, cdm_engine.HandleProvisioningResponse(
binary_protobuf_response, kLevelDefault, &cert,
&wrapped_key));
} else {
ASSERT_EQ(NO_ERROR,
cdm_engine.HandleProvisioningResponse(
http_message, kLevelDefault, &cert, &wrapped_key));
}
}
}
// TODO(fredgc): Replace this with a pre-defined DRM certificate. We could do
// that because either the device is using a known test keybox with a known
// device key, or the device is using an OEM certificate, and we can extract
// that certificate from the provisioning request.
void WvCdmTestBase::EnsureProvisioned() {
CdmSessionId session_id;
wvutil::FileSystem file_system;
// OpenSession will check if a DRM certificate exists, while
// GenerateKeyRequest will actually load the wrapped private key.
// Either may return a NEED_PROVISIONING error, so both have to be checked.
TestCdmEngine cdm_engine(&file_system,
std::shared_ptr<EngineMetrics>(new EngineMetrics));
CdmResponseType status = cdm_engine.OpenSession(config_.key_system(), nullptr,
nullptr, &session_id);
CdmAppParameterMap app_parameters;
CdmKeySetId key_set_id;
InitializationData init_data(ISO_BMFF_VIDEO_MIME_TYPE, binary_key_id());
CdmKeyRequest key_request;
if (status == NO_ERROR) {
status = cdm_engine.GenerateKeyRequest(session_id, key_set_id, init_data,
kLicenseTypeStreaming,
app_parameters, &key_request);
}
// There are situations where we need two provisioning steps.
for (int count = 0; count < 2 && status == NEED_PROVISIONING; count++) {
Provision();
status = cdm_engine.OpenSession(config_.key_system(), nullptr, nullptr,
&session_id);
ASSERT_EQ(NO_ERROR, status);
status = cdm_engine.GenerateKeyRequest(session_id, key_set_id, init_data,
kLicenseTypeStreaming,
app_parameters, &key_request);
}
ASSERT_EQ(KEY_MESSAGE, status);
ASSERT_NE("", session_id) << "Could not open CDM session.";
ASSERT_TRUE(cdm_engine.IsOpenSession(session_id));
ASSERT_EQ(NO_ERROR, cdm_engine.CloseSession(session_id));
}
bool WvCdmTestBase::Initialize(int argc, const char* const argv[],
const std::string& extra_help_text) {
Properties::Init();
bool is_cast_receiver = false;
bool filter_tests = true;
bool show_usage = false;
int verbosity = 0;
// Skip the first element, which is the program name.
const std::vector<std::string> args(argv + 1, argv + argc);
for (const std::string& arg : args) {
if (arg == "--verbose" || arg == "-v") {
++verbosity;
} else if (arg == "--no_filter") {
filter_tests = false;
} else if (arg == "--cast") {
is_cast_receiver = true;
} else if (arg == "--fake_sleep") {
wvutil::TestSleep::set_real_sleep(false);
} else if (arg == "--qa_provisioning") {
use_qa_test_keybox_ = true;
default_config_.set_provisioning_service_certificate(
default_config_.QAProvisioningServiceCertificate());
} else if (arg.find("--gtest") == 0) {
// gtest arguments will be passed to gtest by the main program.
continue;
} else {
const auto index = arg.find('=');
if (index == std::string::npos) {
std::cerr << "Argument values need to be specified using --arg=foo"
<< std::endl;
show_usage = true;
break;
}
const std::string arg_prefix = arg.substr(0, index);
const std::string arg_value = arg.substr(index + 1);
if (arg_prefix == "--license_server_id") {
if (arg_value == "gp") {
default_config_ = ConfigTestEnv(kGooglePlayServer);
} else if (arg_value == "cp") {
default_config_ = ConfigTestEnv(kContentProtectionUatServer);
} else if (arg_value == "st") {
default_config_ = ConfigTestEnv(kContentProtectionStagingServer);
} else {
std::cerr << "Invalid license server id: " << arg_value << std::endl;
show_usage = true;
break;
}
} else if (arg_prefix == "--keyid") {
default_config_.set_key_id(arg_value);
} else if (arg_prefix == "--service_certificate") {
const std::string certificate(wvutil::a2bs_hex(arg_value));
default_config_.set_license_service_certificate(certificate);
} else if (arg_prefix == "--provisioning_certificate") {
const std::string certificate(wvutil::a2bs_hex(arg_value));
default_config_.set_provisioning_service_certificate(certificate);
} else if (arg_prefix == "--license_server_url") {
default_config_.set_license_server(arg_value);
} else if (arg_prefix == "--renewal_server_url") {
default_config_.set_renewal_server(arg_value);
} else if (arg_prefix == "--provisioning_server_url") {
default_config_.set_provisioning_server(arg_value);
} else if (arg_prefix == "--pass") {
default_config_.set_test_pass(std::stoi(arg_value));
std::cout << "Running test pass " << default_config_.test_pass()
<< std::endl;
} else if (arg_prefix == "--initial_time") {
if (wvutil::TestSleep::real_sleep()) {
LOGD("Ignoring initial time %s because using a real clock",
arg_value.c_str());
} else {
LOGE("Setting initial fake clock time to %s", arg_value.c_str());
wvutil::TestSleep::SetFakeClock(stol(arg_value));
}
} else if (arg_prefix == "--test_data_path") {
default_config_.set_test_data_path(arg_value);
} else {
std::cerr << "Unknown argument " << arg_prefix << std::endl;
show_usage = true;
break;
}
}
}
if (show_usage) {
show_menu(argv[0], extra_help_text);
return false;
}
wvutil::g_cutoff = static_cast<wvutil::LogPriority>(verbosity);
// Displays server url, port and key Id being used
std::cout << std::endl;
std::cout << "Default Provisioning Server: "
<< default_config_.provisioning_server() << std::endl;
std::cout << "Default License Server: " << default_config_.license_server()
<< std::endl;
std::cout << "Default Renewal Server: " << default_config_.renewal_server()
<< std::endl;
std::cout << "Default KeyID: " << default_config_.key_id() << std::endl
<< std::endl;
// Figure out which tests are appropriate for OEMCrypto, based on features
// supported.
wvoec::global_features.Initialize();
wvoec::global_features.set_cast_receiver(is_cast_receiver);
// If the user requests --no_filter, we don't change the filter, otherwise, we
// filter out features that are not supported.
if (filter_tests) {
::testing::GTEST_FLAG(filter) =
wvoec::global_features.RestrictFilter(::testing::GTEST_FLAG(filter));
}
return true;
}
std::string MakePSSH(const video_widevine::WidevinePsshData& header) {
std::string data;
header.SerializeToString(&data);
return MakePSSH(data);
}
std::string MakePSSH(const std::string& serialized_header) {
const uint32_t version = 0;
const std::string system_id = InitializationData::WidevineSystemID();
size_t system_id_size = system_id.size();
size_t data_size = serialized_header.size();
size_t atom_size = data_size + system_id_size + 4 * 4;
std::string pssh = wvutil::EncodeUint32(static_cast<uint32_t>(atom_size));
pssh.append("pssh");
pssh.append(wvutil::EncodeUint32(version));
pssh.append(system_id);
pssh.append(wvutil::EncodeUint32(static_cast<uint32_t>(data_size)));
pssh.append(serialized_header);
return pssh;
}
} // namespace wvcdm