Files
android/libwvdrmengine/cdm/core/test/test_base.cpp
Kyle Zhang 45d8b38b43 Do not fall back to L3 if L1 has test keybox
[ Merged of go/wvgerrit/186370 ]

CDM by default allows test keybox from device side.

Bug: 299987160
Bug: 301669353
Change-Id: I06f1936ccd068eb71364a5a8931970954233b686
2023-11-09 00:26:17 +00:00

522 lines
21 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 <openssl/hmac.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 "provisioning_holder.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 << " --server_version=N" << std::endl;
std::cout << " specify the server version. Tests that are not expected "
<< "to pass" << std::endl
<< " on this server version will be skipped." << 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;
}
} // namespace
std::unique_ptr<ConfigTestEnv> WvCdmTestBase::default_config_;
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) {
MaybeInstallTestKeybox();
}
TestCryptoSession::TestCryptoSession(metrics::CryptoMetrics* crypto_metrics,
const TestCryptoSessionConfig* config)
: CryptoSession(crypto_metrics), config_(config) {
MaybeInstallTestKeybox();
}
void TestCryptoSession::MaybeInstallTestKeybox() {
if (IsTestKeyboxNeeded()) {
ReinitializeForTest();
WvCdmTestBase::InstallTestRootOfTrust();
}
}
bool TestCryptoSession::IsTestKeyboxNeeded() {
// The first CryptoSession should have initialized OEMCrypto. This is right
// after that.
if (session_count() != 1) return false;
// If config is not available, assume keybox is required.
if (config_ == nullptr) return true;
// Unless disabled, test keybox is required.
return !config_->disable_test_keybox;
}
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 CdmResponseType(NO_ERROR);
}
class TestCryptoSessionFactory : public CryptoSessionFactory {
public:
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, &session_config_);
}
void SetDisableTestKeybox(bool disable) {
std::unique_lock<std::mutex> auto_lock(init_lock_);
session_config_.disable_test_keybox = disable;
}
private:
std::mutex init_lock_;
// Shared with all TestCryptoSession instances created by this factory.
TestCryptoSessionConfig session_config_;
};
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);
TestCryptoSessionFactory* factory = new TestCryptoSessionFactory();
CryptoSession::SetCryptoSessionFactory(factory);
const char* const disable_test_keybox_flag = getenv("DISABLE_TEST_KEYBOX");
if (disable_test_keybox_flag != nullptr &&
!strcmp(disable_test_keybox_flag, "yes")) {
factory->SetDisableTestKeybox(true);
}
// 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() {
const CdmCertificateType cert_type = kCertificateWidevine;
std::unique_ptr<wvutil::FileSystem> file_system(CreateTestFileSystem());
TestCdmEngine cdm_engine(file_system.get(),
std::shared_ptr<EngineMetrics>(new EngineMetrics));
ProvisioningHolder provisioner(&cdm_engine, config_.provisioning_server(),
config_.provisioning_service_certificate());
provisioner.Provision(cert_type, binary_provisioning_);
}
void WvCdmTestBase::EnsureProvisioned() {
CdmSessionId session_id;
std::unique_ptr<wvutil::FileSystem> file_system(CreateTestFileSystem());
// 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.get(),
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);
if (status == NEED_PROVISIONING) {
continue;
}
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;
default_config_.reset(new ConfigTestEnv(kContentProtectionUatServer));
// 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_.reset(new ConfigTestEnv(kGooglePlayServer));
} else if (arg_value == "cp") {
default_config_.reset(new ConfigTestEnv(kContentProtectionUatServer));
} else if (arg_value == "st") {
default_config_.reset(
new 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 if (arg_prefix == "--server_version") {
default_config_->set_server_version(atoi(arg_value.c_str()));
} 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;
if (default_config_->server_version() != 0) {
std::cout << "Server Version: " << default_config_->server_version()
<< std::endl;
}
std::cout << std::endl;
// Figure out which tests are appropriate for OEMCrypto, based on features
// supported.
wvoec::global_features.Initialize();
if (is_cast_receiver) {
// Turn it on if passed in on the command line. Do not turn these tests off
// automtically -- instead, we'll let the caller filter them out if they
// need to. These tests will normally only run if the device claims to
// support being a cast receiver.
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