From d1dff772d30efb87457bf1ddba5eddfb9801e075 Mon Sep 17 00:00:00 2001 From: Rahul Frias Date: Mon, 2 Nov 2020 00:11:57 -0800 Subject: [PATCH] Add some policy integration tests Cherry-pick from branch rvc-dev of http://go/wvgerrit/105824 and rvc-widevine-release http://go/ag/12561660 This adds two policy integration tests to verify that we are handling offline licenses correctly. Bug: 161023174 Bug: 129301787 Test: WV unit/integration tests Change-Id: I20f5d6a9fbfd2ff8cff361e1005e45b46c700704 --- .../build_and_run_all_unit_tests.sh | 1 + libwvdrmengine/cdm/core/include/cdm_engine.h | 5 - .../cdm/core/test/cdm_engine_test.cpp | 10 +- .../cdm/core/test/generic_crypto_unittest.cpp | 10 +- .../cdm/core/test/policy_integration_test.cpp | 195 ++++++++++++++++++ libwvdrmengine/cdm/core/test/test_base.cpp | 12 +- libwvdrmengine/cdm/core/test/test_base.h | 21 ++ libwvdrmengine/cdm/test/Android.mk | 5 + 8 files changed, 232 insertions(+), 27 deletions(-) create mode 100644 libwvdrmengine/cdm/core/test/policy_integration_test.cpp diff --git a/libwvdrmengine/build_and_run_all_unit_tests.sh b/libwvdrmengine/build_and_run_all_unit_tests.sh index eaf8ff1c..c860072a 100755 --- a/libwvdrmengine/build_and_run_all_unit_tests.sh +++ b/libwvdrmengine/build_and_run_all_unit_tests.sh @@ -60,6 +60,7 @@ WV_TEST_TARGETS="base64_test \ odk_test \ policy_engine_constraints_unittest \ policy_engine_unittest \ + policy_integration_test \ request_license_test \ rw_lock_test \ service_certificate_unittest \ diff --git a/libwvdrmengine/cdm/core/include/cdm_engine.h b/libwvdrmengine/cdm/core/include/cdm_engine.h index 6f9334a2..d94cccee 100644 --- a/libwvdrmengine/cdm/core/include/cdm_engine.h +++ b/libwvdrmengine/cdm/core/include/cdm_engine.h @@ -347,11 +347,6 @@ class CdmEngine { protected: friend class CdmEngineFactory; - friend class ParallelCdmTest; - friend class WvCdmEnginePreProvTest; - friend class WvCdmTestBase; - friend class WvGenericCryptoTest; - friend class TestLicenseHolder; CdmEngine(FileSystem* file_system, std::shared_ptr metrics); diff --git a/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp b/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp index 52f068ce..115d760c 100644 --- a/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp +++ b/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp @@ -48,12 +48,9 @@ const std::string kFakeSessionId = "TotallyARealSession123456789"; } // namespace -class WvCdmEnginePreProvTest : public WvCdmTestBase { +class WvCdmEnginePreProvTest : public WvCdmTestBaseWithEngine { public: - WvCdmEnginePreProvTest() - : dummy_engine_metrics_(new EngineMetrics), - cdm_engine_(&file_system_, dummy_engine_metrics_), - session_opened_(false) {} + WvCdmEnginePreProvTest() : session_opened_(false) {} ~WvCdmEnginePreProvTest() override {} @@ -116,9 +113,6 @@ class WvCdmEnginePreProvTest : public WvCdmTestBase { return true; } - FileSystem file_system_; - shared_ptr dummy_engine_metrics_; - CdmEngine cdm_engine_; bool session_opened_; std::string key_msg_; std::string session_id_; diff --git a/libwvdrmengine/cdm/core/test/generic_crypto_unittest.cpp b/libwvdrmengine/cdm/core/test/generic_crypto_unittest.cpp index 7e796b11..26b9f306 100644 --- a/libwvdrmengine/cdm/core/test/generic_crypto_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/generic_crypto_unittest.cpp @@ -29,12 +29,9 @@ namespace wvcdm { -class WvGenericCryptoTest : public WvCdmTestBase { +class WvGenericCryptoTest : public WvCdmTestBaseWithEngine { public: - WvGenericCryptoTest() - : dummy_engine_metrics_(new metrics::EngineMetrics), - cdm_engine_(&file_system_, dummy_engine_metrics_), - holder_(&cdm_engine_) {} + WvGenericCryptoTest() : holder_(&cdm_engine_) {} void SetUp() override { WvCdmTestBase::SetUp(); @@ -74,9 +71,6 @@ class WvGenericCryptoTest : public WvCdmTestBase { } protected: - FileSystem file_system_; - std::shared_ptr dummy_engine_metrics_; - CdmEngine cdm_engine_; TestLicenseHolder holder_; KeyId ency_id_; diff --git a/libwvdrmengine/cdm/core/test/policy_integration_test.cpp b/libwvdrmengine/cdm/core/test/policy_integration_test.cpp new file mode 100644 index 00000000..a198860a --- /dev/null +++ b/libwvdrmengine/cdm/core/test/policy_integration_test.cpp @@ -0,0 +1,195 @@ +// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. + +// These tests perform various end-to-end actions similar to what an application +// would do. They verify that policies specified on UAT are honored on the +// device. + +#include +#include + +#include +#include + +#include "cdm_engine.h" +#include "clock.h" +#include "config_test_env.h" +#include "initialization_data.h" +#include "license_request.h" +#include "log.h" +#include "metrics_collections.h" +#include "test_base.h" +#include "test_printers.h" +#include "test_sleep.h" +#include "url_request.h" +#include "wv_cdm_constants.h" +#include "wv_cdm_types.h" + +namespace wvcdm { + +namespace { +constexpr int kHttpOk = 200; +const std::string kCencMimeType = "cenc"; +} // namespace + +// Core Policy Integration Test +class CorePIGTest : public WvCdmTestBaseWithEngine { + protected: + void SetUp() override { + WvCdmTestBase::SetUp(); + EnsureProvisioned(); + } + + void OpenSession(CdmSessionId* session_id) { + CdmResponseType status = cdm_engine_.OpenSession( + config_.key_system(), nullptr, nullptr, session_id); + ASSERT_EQ(NO_ERROR, status); + ASSERT_TRUE(cdm_engine_.IsOpenSession(*session_id)); + } + + void CloseSession(const CdmSessionId& session_id) { + CdmResponseType status = cdm_engine_.CloseSession(session_id); + ASSERT_EQ(NO_ERROR, status); + ASSERT_FALSE(cdm_engine_.IsOpenSession(session_id)); + } + + // Create a license request for the given content_id and requesting the + // specified license_type. + void GenerateKeyRequest(const CdmSessionId& session_id, + const std::string& content_id, + CdmKeyRequest* key_request, + CdmLicenseType license_type) { + video_widevine::WidevinePsshData pssh; + pssh.set_content_id(content_id); + const std::string init_data_string = MakePSSH(pssh); + const InitializationData init_data(kCencMimeType, init_data_string); + init_data.DumpToLogs(); + CdmAppParameterMap empty_app_parameters; + CdmKeySetId empty_key_set_id; + CdmResponseType result = cdm_engine_.GenerateKeyRequest( + session_id, empty_key_set_id, init_data, license_type, + empty_app_parameters, key_request); + ASSERT_EQ(KEY_MESSAGE, result); + ASSERT_EQ(kKeyRequestTypeInitial, key_request->type); + } + + // Send the request to the server and get the response. + void GetKeyResponse(const CdmKeyRequest& key_request, + std::string* key_response) { + const std::string url = config_.license_server() + config_.client_auth(); + UrlRequest url_request(url); + ASSERT_TRUE(url_request.is_connected()); + + std::string http_response; + url_request.PostRequest(key_request.message); + ASSERT_TRUE(url_request.GetResponse(&http_response)); + int status_code = url_request.GetStatusCode(http_response); + ASSERT_EQ(kHttpOk, status_code); + + LicenseRequest license_request; + license_request.GetDrmMessage(http_response, *key_response); + } + + // Load the license response into the specified session. Verify it has the + // correct license type (either streaming or offline). + void AddKey(const CdmSessionId& session_id, const std::string& key_response, + CdmLicenseType expected_license_type, CdmKeySetId* key_set_id) { + CdmLicenseType license_type; + CdmResponseType status = + cdm_engine_.AddKey(session_id, key_response, &license_type, key_set_id); + ASSERT_EQ(KEY_ADDED, status); + ASSERT_EQ(expected_license_type, license_type); + } + + // Reload the license response into the specified session. + void RestoreKey(const CdmSessionId& session_id, + const CdmKeySetId& key_set_id) { + CdmResponseType status = cdm_engine_.RestoreKey(session_id, key_set_id); + ASSERT_EQ(KEY_ADDED, status); + } + + // Use the key to decrypt. + void Decrypt(const CdmSessionId& session_id, const KeyId& key_id) { + constexpr size_t buffer_size = 500; + const std::vector input(buffer_size, 0); + std::vector output(buffer_size, 0); + const std::vector iv(KEY_IV_SIZE, 0); + Decrypt(session_id, key_id, input, iv, &output, NO_ERROR); + } + + // Try to use the key to decrypt, but expect the key has expired. + void FailDecrypt(const CdmSessionId& session_id, const KeyId& key_id) { + constexpr size_t buffer_size = 500; + const std::vector input(buffer_size, 0); + std::vector output(buffer_size, 0); + const std::vector iv(KEY_IV_SIZE, 0); + Decrypt(session_id, key_id, input, iv, &output, NEED_KEY); + } + + void Decrypt(const CdmSessionId& session_id, const KeyId& key_id, + const std::vector& input, + const std::vector& iv, std::vector* output, + CdmResponseType expected_status) { + CdmDecryptionParametersV16 params(key_id); + params.is_secure = false; + CdmDecryptionSample sample(input.data(), output->data(), 0, input.size(), + iv); + CdmDecryptionSubsample subsample(0, input.size()); + sample.subsamples.push_back(subsample); + params.samples.push_back(sample); + + CdmResponseType status = cdm_engine_.DecryptV16(session_id, params); + ASSERT_EQ(expected_status, status); + } +}; + +// An offline license with nonce not required. +TEST_F(CorePIGTest, OfflineNoNonce) { + const std::string content_id = "2015_tears"; + const KeyId key_id = "0000000000000000"; + + const CdmLicenseType license_type = kLicenseTypeOffline; + CdmSessionId session_id; + ASSERT_NO_FATAL_FAILURE(OpenSession(&session_id)); + CdmKeyRequest key_request; + ASSERT_NO_FATAL_FAILURE( + GenerateKeyRequest(session_id, content_id, &key_request, license_type)); + std::string key_response; + ASSERT_NO_FATAL_FAILURE(GetKeyResponse(key_request, &key_response)); + CdmKeySetId key_set_id; + ASSERT_NO_FATAL_FAILURE( + AddKey(session_id, key_response, license_type, &key_set_id)); + ASSERT_NO_FATAL_FAILURE(Decrypt(session_id, key_id)); + ASSERT_NO_FATAL_FAILURE(CloseSession(session_id)); + ASSERT_NO_FATAL_FAILURE(OpenSession(&session_id)); + ASSERT_NO_FATAL_FAILURE(RestoreKey(session_id, key_set_id)); + ASSERT_NO_FATAL_FAILURE(Decrypt(session_id, key_id)); + ASSERT_NO_FATAL_FAILURE(CloseSession(session_id)); +} + +// An offline license with nonce and provider session token. +TEST_F(CorePIGTest, OfflineWithPST) { + const std::string content_id = "offline_clip2"; + const KeyId key_id = + "\x32\x60\xF3\x9E\x12\xCC\xF6\x53\x52\x99\x90\x16\x8A\x35\x83\xFF"; + const CdmLicenseType license_type = kLicenseTypeOffline; + CdmSessionId session_id; + ASSERT_NO_FATAL_FAILURE(OpenSession(&session_id)); + CdmKeyRequest key_request; + ASSERT_NO_FATAL_FAILURE( + GenerateKeyRequest(session_id, content_id, &key_request, license_type)); + std::string key_response; + ASSERT_NO_FATAL_FAILURE(GetKeyResponse(key_request, &key_response)); + CdmKeySetId key_set_id; + ASSERT_NO_FATAL_FAILURE( + AddKey(session_id, key_response, license_type, &key_set_id)); + ASSERT_NO_FATAL_FAILURE(Decrypt(session_id, key_id)); + ASSERT_NO_FATAL_FAILURE(CloseSession(session_id)); + ASSERT_NO_FATAL_FAILURE(OpenSession(&session_id)); + ASSERT_NO_FATAL_FAILURE(RestoreKey(session_id, key_set_id)); + ASSERT_NO_FATAL_FAILURE(Decrypt(session_id, key_id)); + ASSERT_NO_FATAL_FAILURE(CloseSession(session_id)); +} + +} // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/test/test_base.cpp b/libwvdrmengine/cdm/core/test/test_base.cpp index f225e24e..36957ebc 100644 --- a/libwvdrmengine/cdm/core/test/test_base.cpp +++ b/libwvdrmengine/cdm/core/test/test_base.cpp @@ -290,8 +290,8 @@ void WvCdmTestBase::Provision() { if (config_.provisioning_server() == "fake") { LOGD("Using fake provisioning server."); - CdmEngine cdm_engine(&file_system, - std::shared_ptr(new EngineMetrics)); + TestCdmEngine cdm_engine(&file_system, + std::shared_ptr(new EngineMetrics)); FakeProvisioningServer server; CdmResponseType result = cdm_engine.GetProvisioningRequest( cert_type, cert_authority, server.service_certificate(), kLevelDefault, @@ -309,8 +309,8 @@ void WvCdmTestBase::Provision() { EXPECT_EQ(NO_ERROR, result); } else { // TODO(fredgc): provision for different SPOIDs. - CdmEngine cdm_engine(&file_system, - std::shared_ptr(new EngineMetrics)); + TestCdmEngine cdm_engine(&file_system, + std::shared_ptr(new EngineMetrics)); CdmResponseType result = cdm_engine.GetProvisioningRequest( cert_type, cert_authority, config_.provisioning_service_certificate(), @@ -387,8 +387,8 @@ void WvCdmTestBase::Provision() { void WvCdmTestBase::EnsureProvisioned() { CdmSessionId session_id; FileSystem file_system; - CdmEngine cdm_engine(&file_system, - std::shared_ptr(new EngineMetrics)); + TestCdmEngine cdm_engine(&file_system, + std::shared_ptr(new EngineMetrics)); CdmResponseType status = cdm_engine.OpenSession(config_.key_system(), nullptr, nullptr, &session_id); if (status == NEED_PROVISIONING) { diff --git a/libwvdrmengine/cdm/core/test/test_base.h b/libwvdrmengine/cdm/core/test/test_base.h index e1553ee5..4293bd97 100644 --- a/libwvdrmengine/cdm/core/test/test_base.h +++ b/libwvdrmengine/cdm/core/test/test_base.h @@ -76,6 +76,27 @@ class WvCdmTestBase : public ::testing::Test { bool binary_provisioning_; }; +// This just makes the constructor public so that we can create one with dummy +// metrics and file system. +class TestCdmEngine : public CdmEngine { + public: + TestCdmEngine(FileSystem* file_system, + std::shared_ptr metrics) + : CdmEngine(file_system, metrics) {} +}; + +class WvCdmTestBaseWithEngine : public WvCdmTestBase { + public: + WvCdmTestBaseWithEngine() + : dummy_engine_metrics_(new metrics::EngineMetrics()), + cdm_engine_(&file_system_, dummy_engine_metrics_) {} + + protected: + FileSystem file_system_; + shared_ptr dummy_engine_metrics_; + TestCdmEngine cdm_engine_; +}; + class TestCryptoSession : public CryptoSession { public: explicit TestCryptoSession(metrics::CryptoMetrics* crypto_metrics); diff --git a/libwvdrmengine/cdm/test/Android.mk b/libwvdrmengine/cdm/test/Android.mk index f66b16bc..7fac72a9 100644 --- a/libwvdrmengine/cdm/test/Android.mk +++ b/libwvdrmengine/cdm/test/Android.mk @@ -116,6 +116,11 @@ test_src_dir := ../core/test test_main := ../core/test/test_main.cpp include $(LOCAL_PATH)/integration-test.mk +test_name := policy_integration_test +test_src_dir := ../core/test +test_main := ../core/test/test_main.cpp +include $(LOCAL_PATH)/integration-test.mk + test_name := request_license_test test_src_dir := . test_main := ../core/test/test_main.cpp