[ Merge of http://go/wvgerrit/168357 and http://go/wvgerrit/168177 ] When we get an error from the provisioning server while running a test, we should log extra provisioning information. Bug: 273990016 Test: GtsMediaTestCases Change-Id: I44095261e07ae079c632873f254d8e6879bab8c3
713 lines
28 KiB
C++
713 lines
28 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 "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;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
// TODO(b/242744857): This extra debugging may not be needed in all cases. When
|
|
// provisioning fails, this dumps the cert and other information.
|
|
std::string DumpProvAttempt(const std::string& url, const std::string& request,
|
|
const std::string& http_message,
|
|
CdmEngine* cdm_engine) {
|
|
std::stringstream info;
|
|
info << "Provisioning url: " << url << "\n";
|
|
info << "Request: " << wvutil::unlimited_b2a_hex(request) << "\n";
|
|
info << "http_message: " << wvutil::unlimited_b2a_hex(http_message) << "\n";
|
|
std::string system_id;
|
|
cdm_engine->QueryStatus(kLevelDefault, QUERY_KEY_SYSTEM_ID, &system_id);
|
|
info << "system id: " << system_id << "\n";
|
|
if (wvoec::global_features.derive_key_method ==
|
|
wvoec::DeviceFeatures::TEST_PROVISION_30) {
|
|
std::vector<uint8_t> cert;
|
|
size_t cert_length = 0;
|
|
OEMCryptoResult result = OEMCrypto_GetOEMPublicCertificate(
|
|
cert.data(), &cert_length, kLevelDefault);
|
|
if (result == OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
cert.resize(cert_length);
|
|
result = OEMCrypto_GetOEMPublicCertificate(cert.data(), &cert_length,
|
|
kLevelDefault);
|
|
}
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
info << "--- ERROR GETTING CERT. result=" << result;
|
|
} else {
|
|
info << "OEM Cert = (len=" << cert_length << ") "
|
|
<< wvutil::unlimited_b2a_hex(cert);
|
|
}
|
|
}
|
|
if (wvoec::global_features.derive_key_method ==
|
|
wvoec::DeviceFeatures::TEST_PROVISION_40) {
|
|
std::vector<uint8_t> bcc;
|
|
size_t bcc_length = 0;
|
|
std::vector<uint8_t> signature;
|
|
size_t signature_length = 0;
|
|
OEMCryptoResult result = OEMCrypto_GetBootCertificateChain(
|
|
bcc.data(), &bcc_length, signature.data(), &signature_length);
|
|
if (result == OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
bcc.resize(bcc_length);
|
|
signature.resize(signature_length);
|
|
result = OEMCrypto_GetBootCertificateChain(
|
|
bcc.data(), &bcc_length, signature.data(), &signature_length);
|
|
}
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
info << "--- ERROR GETTING BCC. result=" << result;
|
|
} else {
|
|
info << "BCC = (len=" << bcc_length << ") "
|
|
<< wvutil::unlimited_b2a_hex(bcc) << "\n"
|
|
<< "Additional Sig = (len=" << signature_length << ") "
|
|
<< wvutil::unlimited_b2a_hex(signature) << "\n";
|
|
}
|
|
}
|
|
return info.str();
|
|
}
|
|
} // 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()) {
|
|
CryptoSession::SetAllowTestKeybox(true);
|
|
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() {
|
|
CdmProvisioningRequest prov_request;
|
|
std::string provisioning_server_url;
|
|
CdmCertificateType cert_type = kCertificateWidevine;
|
|
std::string cert_authority;
|
|
std::string cert, wrapped_key;
|
|
|
|
CdmSessionId session_id;
|
|
std::unique_ptr<wvutil::FileSystem> file_system(CreateTestFileSystem());
|
|
|
|
if (config_.provisioning_server() == "fake") {
|
|
LOGD("Using fake provisioning server.");
|
|
|
|
TestCdmEngine cdm_engine(file_system.get(),
|
|
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.get(),
|
|
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_NO_FATAL_FAILURE(url_request.AssertOkResponse(&http_message))
|
|
<< "Failed to fetch provisioning response. "
|
|
<< DumpProvAttempt(provisioning_server_url, prov_request, http_message,
|
|
&cdm_engine);
|
|
|
|
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))
|
|
<< "Binary provisioning failed. "
|
|
<< DumpProvAttempt(provisioning_server_url, prov_request,
|
|
http_message, &cdm_engine);
|
|
} else {
|
|
ASSERT_EQ(NO_ERROR, cdm_engine.HandleProvisioningResponse(
|
|
http_message, kLevelDefault, &cert, &wrapped_key))
|
|
<< "Non-binary provisioning failed. "
|
|
<< DumpProvAttempt(provisioning_server_url, prov_request,
|
|
http_message, &cdm_engine);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
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();
|
|
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
|