Merge "Correct stability issues for SPOIDs for provisioning 4.0" into udc-qpr-dev am: 05f045d802
Original change: https://googleplex-android-review.googlesource.com/c/platform/vendor/widevine/+/24537206 Change-Id: Ifd57483a874109b90736654f021f6bbcd3422f0d Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -230,6 +230,14 @@ cc_library_static {
|
|||||||
min_sdk_version: "34",
|
min_sdk_version: "34",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filegroup {
|
||||||
|
name:"libwvdrmengine_src",
|
||||||
|
srcs: [
|
||||||
|
"src/WVCDMSingleton.cpp",
|
||||||
|
"src/Utils.cpp",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// Builds libwvaidl.so
|
// Builds libwvaidl.so
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ WV_UNITTESTS="base64_test \
|
|||||||
cdm_usage_table_unittest \
|
cdm_usage_table_unittest \
|
||||||
certificate_provisioning_unittest \
|
certificate_provisioning_unittest \
|
||||||
counter_metric_unittest \
|
counter_metric_unittest \
|
||||||
|
core_integration_test \
|
||||||
crypto_session_unittest \
|
crypto_session_unittest \
|
||||||
device_files_unittest \
|
device_files_unittest \
|
||||||
distribution_unittest \
|
distribution_unittest \
|
||||||
@@ -71,7 +72,8 @@ WV_UNITTESTS="base64_test \
|
|||||||
system_id_extractor_unittest \
|
system_id_extractor_unittest \
|
||||||
timer_unittest \
|
timer_unittest \
|
||||||
value_metric_unittest \
|
value_metric_unittest \
|
||||||
wv_cdm_metrics_test"
|
wv_cdm_metrics_test \
|
||||||
|
wv_plugin_test"
|
||||||
|
|
||||||
cd $ANDROID_BUILD_TOP
|
cd $ANDROID_BUILD_TOP
|
||||||
pwd
|
pwd
|
||||||
|
|||||||
@@ -150,7 +150,12 @@ CdmResponseType CertificateProvisioning::SetSpoidParameter(
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
request->set_stable_id(device_unique_id + origin);
|
request->set_stable_id(device_unique_id + origin);
|
||||||
} // No else clause, by design. It is valid to do nothing.
|
} else {
|
||||||
|
// It is valid to do nothing for legacy devices. For most recently
|
||||||
|
// launched devices this is an error. For now, we will log
|
||||||
|
// but not return an error.
|
||||||
|
LOGE("No spoid/provider id/stable id set");
|
||||||
|
}
|
||||||
return CdmResponseType(NO_ERROR);
|
return CdmResponseType(NO_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,9 +383,8 @@ CdmResponseType CertificateProvisioning::GetProvisioning40RequestInternal(
|
|||||||
|
|
||||||
// Since |stored_oem_cert| is empty, the client identification token will be
|
// Since |stored_oem_cert| is empty, the client identification token will be
|
||||||
// retrieved from OEMCrypto, which is the BCC in this case.
|
// retrieved from OEMCrypto, which is the BCC in this case.
|
||||||
status = FillEncryptedClientIdWithAdditionalParameter(
|
status = FillEncryptedClientId(stored_oem_cert, provisioning_request,
|
||||||
stored_oem_cert, additional_parameter, provisioning_request,
|
wv_service_cert);
|
||||||
wv_service_cert);
|
|
||||||
if (status != NO_ERROR) return status;
|
if (status != NO_ERROR) return status;
|
||||||
} else {
|
} else {
|
||||||
// This is the second stage provisioning.
|
// This is the second stage provisioning.
|
||||||
|
|||||||
@@ -150,7 +150,8 @@ CdmResponseType ClientIdentification::Prepare(
|
|||||||
}
|
}
|
||||||
|
|
||||||
ClientIdentification_NameValue* client_info;
|
ClientIdentification_NameValue* client_info;
|
||||||
if (is_license_request_) {
|
// Include app parameters for license and provisioning requests
|
||||||
|
if (!is_okp_request_) {
|
||||||
CdmAppParameterMap::const_iterator iter;
|
CdmAppParameterMap::const_iterator iter;
|
||||||
for (iter = app_parameters.begin(); iter != app_parameters.end(); ++iter) {
|
for (iter = app_parameters.begin(); iter != app_parameters.end(); ++iter) {
|
||||||
if (IsPropertyKeyReserved(iter->first)) {
|
if (IsPropertyKeyReserved(iter->first)) {
|
||||||
|
|||||||
237
libwvdrmengine/cdm/core/test/core_integration_test.cpp
Normal file
237
libwvdrmengine/cdm/core/test/core_integration_test.cpp
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
// 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 "certificate_provisioning.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "provisioning_holder.h"
|
||||||
|
#include "test_base.h"
|
||||||
|
#include "wv_cdm_types.h"
|
||||||
|
|
||||||
|
static const std::string kTestAppPackageName1 = "test_app_package_1";
|
||||||
|
static const std::string kTestAppPackageName2 = "test_app_package_2";
|
||||||
|
static const std::string kTestOrigin1 = "test_origin_1";
|
||||||
|
static const std::string kTestOrigin2 = "test_origin_2";
|
||||||
|
|
||||||
|
namespace wvcdm {
|
||||||
|
|
||||||
|
class CoreIntegrationTest : public WvCdmTestBaseWithEngine {
|
||||||
|
public:
|
||||||
|
void SetUp() override {
|
||||||
|
WvCdmTestBase::SetUp();
|
||||||
|
EnsureProvisioned();
|
||||||
|
}
|
||||||
|
|
||||||
|
using WvCdmTestBase::Provision;
|
||||||
|
virtual bool Provision(const std::string app_package_name, std::string* spoid,
|
||||||
|
std::string* drm_certificate_serial_number) {
|
||||||
|
if (spoid == nullptr) {
|
||||||
|
ADD_FAILURE() << "|spoid| not provided";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (drm_certificate_serial_number == nullptr) {
|
||||||
|
ADD_FAILURE() << "|drm_certificate_serial_number| not provided";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string device_id;
|
||||||
|
CdmResponseType sts =
|
||||||
|
cdm_engine_.QueryStatus(kLevelDefault, QUERY_KEY_DEVICE_ID, &device_id);
|
||||||
|
|
||||||
|
if (!sts.IsOk()) {
|
||||||
|
ADD_FAILURE() << "Query Device ID failed: " << sts.code();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string value;
|
||||||
|
sts = cdm_engine_.QueryStatus(kLevelDefault, QUERY_KEY_SYSTEM_ID, &value);
|
||||||
|
|
||||||
|
if (!sts.IsOk()) {
|
||||||
|
ADD_FAILURE() << "Query System ID failed: " << sts.code();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
long val = std::strtol(value.c_str(), nullptr, 10);
|
||||||
|
if (val <= 0 || val > std::numeric_limits<uint32_t>::max()) {
|
||||||
|
ADD_FAILURE() << "System ID invalid: " << val;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t system_id = static_cast<uint32_t>(val);
|
||||||
|
|
||||||
|
*spoid = device_id + app_package_name;
|
||||||
|
cdm_engine_.SetSpoid(*spoid);
|
||||||
|
|
||||||
|
ProvisioningHolder provisioner(&cdm_engine_, config_.provisioning_server(),
|
||||||
|
config_.provisioning_service_certificate());
|
||||||
|
if (system_id == wvcdm::NULL_SYSTEM_ID) {
|
||||||
|
// Either first stage prov 4.0 or OKP
|
||||||
|
provisioner.Provision(binary_provisioning_);
|
||||||
|
}
|
||||||
|
provisioner.Provision(binary_provisioning_);
|
||||||
|
|
||||||
|
if (!GetDrmCertificateSerialNumber(provisioner.response(),
|
||||||
|
drm_certificate_serial_number)) {
|
||||||
|
ADD_FAILURE() << "Failed to get DRM Certificate Serial Number: "
|
||||||
|
<< sts.code();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool Provision(const std::string app_package_name,
|
||||||
|
const std::string origin, std::string* spoid,
|
||||||
|
std::string* drm_certificate_serial_number) {
|
||||||
|
std::string name = app_package_name + origin;
|
||||||
|
return Provision(name, spoid, drm_certificate_serial_number);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool GetDrmCertificateSerialNumber(
|
||||||
|
const wvcdm::CdmProvisioningResponse& response,
|
||||||
|
std::string* serial_number) {
|
||||||
|
if (serial_number == nullptr) {
|
||||||
|
ADD_FAILURE() << "|serial_number| not provided";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
wvcdm::CdmProvisioningResponse provisioning_response;
|
||||||
|
if (!wvcdm::CertificateProvisioning::ExtractAndDecodeSignedMessage(
|
||||||
|
response, &provisioning_response)) {
|
||||||
|
ADD_FAILURE() << "Failed to decode signed provisioning message";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
video_widevine::SignedProvisioningMessage signed_response;
|
||||||
|
if (!signed_response.ParseFromString(provisioning_response) ||
|
||||||
|
!signed_response.has_message()) {
|
||||||
|
ADD_FAILURE() << "Failed to parse signed provisioning response";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
video_widevine::ProvisioningResponse prov_response;
|
||||||
|
if (!prov_response.ParseFromString(signed_response.message()) ||
|
||||||
|
!prov_response.has_device_certificate()) {
|
||||||
|
ADD_FAILURE() << "Failed to parse provisioning response";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!wvcdm::CertificateProvisioning::ExtractDeviceInfo(
|
||||||
|
prov_response.device_certificate(), serial_number, nullptr, nullptr,
|
||||||
|
nullptr)) {
|
||||||
|
ADD_FAILURE() << "Failed to extract device information";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Verify that SPOIDs and DRM certificate serial number are stable between
|
||||||
|
// factory resets/provisioning attempts for the same app and different between
|
||||||
|
// different apps. Test using two different apps and origins.
|
||||||
|
TEST_F(CoreIntegrationTest, ProvisioningStableSpoidTest) {
|
||||||
|
std::string level;
|
||||||
|
ASSERT_EQ(
|
||||||
|
NO_ERROR,
|
||||||
|
cdm_engine_.QueryStatus(kLevelDefault, QUERY_KEY_SECURITY_LEVEL, &level)
|
||||||
|
.code());
|
||||||
|
|
||||||
|
ASSERT_TRUE(level == QUERY_VALUE_SECURITY_LEVEL_L1 ||
|
||||||
|
level == QUERY_VALUE_SECURITY_LEVEL_L3)
|
||||||
|
<< "Unknown security level: " << level;
|
||||||
|
|
||||||
|
CdmSecurityLevel security_level = level == QUERY_VALUE_SECURITY_LEVEL_L1
|
||||||
|
? kSecurityLevelL1
|
||||||
|
: kSecurityLevelL3;
|
||||||
|
|
||||||
|
// App 1, first provisioning attempt
|
||||||
|
std::string spoid_app_1[2];
|
||||||
|
std::string drm_cert_serial_number_app_1[2];
|
||||||
|
ASSERT_TRUE(Provision(kTestAppPackageName1, &spoid_app_1[0],
|
||||||
|
&drm_cert_serial_number_app_1[0]));
|
||||||
|
|
||||||
|
cdm_engine_.Unprovision(security_level);
|
||||||
|
|
||||||
|
// second provisioning attempt
|
||||||
|
ASSERT_TRUE(Provision(kTestAppPackageName1, &spoid_app_1[1],
|
||||||
|
&drm_cert_serial_number_app_1[1]));
|
||||||
|
|
||||||
|
// App 2, first provisioning attempt
|
||||||
|
std::string spoid_app_2[2];
|
||||||
|
std::string drm_cert_serial_number_app_package_name_2[2];
|
||||||
|
ASSERT_TRUE(Provision(kTestAppPackageName2, &spoid_app_2[0],
|
||||||
|
&drm_cert_serial_number_app_package_name_2[0]));
|
||||||
|
|
||||||
|
cdm_engine_.Unprovision(security_level);
|
||||||
|
|
||||||
|
// second provisioning attempt
|
||||||
|
ASSERT_TRUE(Provision(kTestAppPackageName2, &spoid_app_2[1],
|
||||||
|
&drm_cert_serial_number_app_package_name_2[1]));
|
||||||
|
|
||||||
|
// App 1 and Origin 1, first provisioning attempt
|
||||||
|
std::string spoid_app_1_origin_1[2];
|
||||||
|
std::string drm_cert_serial_number_app_1_origin_1[2];
|
||||||
|
ASSERT_TRUE(Provision(kTestAppPackageName1, kTestOrigin1,
|
||||||
|
&spoid_app_1_origin_1[0],
|
||||||
|
&drm_cert_serial_number_app_1_origin_1[0]));
|
||||||
|
|
||||||
|
cdm_engine_.Unprovision(security_level);
|
||||||
|
|
||||||
|
// second provisioning attempt
|
||||||
|
ASSERT_TRUE(Provision(kTestAppPackageName1, kTestOrigin1,
|
||||||
|
&spoid_app_1_origin_1[1],
|
||||||
|
&drm_cert_serial_number_app_1_origin_1[1]));
|
||||||
|
|
||||||
|
// App 1 and Origin 2, first provisioning attempt
|
||||||
|
std::string spoid_app_1_origin_2[2];
|
||||||
|
std::string drm_cert_serial_number_app_1_origin_2[2];
|
||||||
|
ASSERT_TRUE(Provision(kTestAppPackageName1, kTestOrigin2,
|
||||||
|
&spoid_app_1_origin_2[0],
|
||||||
|
&drm_cert_serial_number_app_1_origin_2[0]));
|
||||||
|
|
||||||
|
cdm_engine_.Unprovision(security_level);
|
||||||
|
|
||||||
|
// second provisioning attempt
|
||||||
|
ASSERT_TRUE(Provision(kTestAppPackageName1, kTestOrigin2,
|
||||||
|
&spoid_app_1_origin_2[1],
|
||||||
|
&drm_cert_serial_number_app_1_origin_2[1]));
|
||||||
|
|
||||||
|
// Verify that SPOID and DRM cert are stable
|
||||||
|
ASSERT_EQ(spoid_app_1[0], spoid_app_1[1]);
|
||||||
|
ASSERT_EQ(spoid_app_2[0], spoid_app_2[1]);
|
||||||
|
ASSERT_EQ(spoid_app_1_origin_1[0], spoid_app_1_origin_1[1]);
|
||||||
|
ASSERT_EQ(spoid_app_1_origin_2[0], spoid_app_1_origin_2[1]);
|
||||||
|
|
||||||
|
ASSERT_EQ(drm_cert_serial_number_app_1[0], drm_cert_serial_number_app_1[1]);
|
||||||
|
ASSERT_EQ(drm_cert_serial_number_app_package_name_2[0],
|
||||||
|
drm_cert_serial_number_app_package_name_2[1]);
|
||||||
|
ASSERT_EQ(drm_cert_serial_number_app_1_origin_1[0],
|
||||||
|
drm_cert_serial_number_app_1_origin_1[1]);
|
||||||
|
ASSERT_EQ(drm_cert_serial_number_app_1_origin_2[0],
|
||||||
|
drm_cert_serial_number_app_1_origin_2[1]);
|
||||||
|
|
||||||
|
// Verify that SPOID and DRM cert do not match different apps/origins
|
||||||
|
ASSERT_NE(spoid_app_1[0], spoid_app_2[0]);
|
||||||
|
ASSERT_NE(spoid_app_1[0], spoid_app_1_origin_1[0]);
|
||||||
|
ASSERT_NE(spoid_app_1[0], spoid_app_1_origin_2[0]);
|
||||||
|
ASSERT_NE(spoid_app_2[0], spoid_app_1_origin_1[0]);
|
||||||
|
ASSERT_NE(spoid_app_2[0], spoid_app_1_origin_2[0]);
|
||||||
|
ASSERT_NE(spoid_app_1_origin_1[0], spoid_app_1_origin_2[0]);
|
||||||
|
|
||||||
|
ASSERT_NE(drm_cert_serial_number_app_1[0],
|
||||||
|
drm_cert_serial_number_app_package_name_2[0]);
|
||||||
|
ASSERT_NE(drm_cert_serial_number_app_1[0],
|
||||||
|
drm_cert_serial_number_app_1_origin_1[0]);
|
||||||
|
ASSERT_NE(drm_cert_serial_number_app_1[0],
|
||||||
|
drm_cert_serial_number_app_1_origin_2[0]);
|
||||||
|
ASSERT_NE(drm_cert_serial_number_app_package_name_2[0],
|
||||||
|
drm_cert_serial_number_app_1_origin_1[0]);
|
||||||
|
ASSERT_NE(drm_cert_serial_number_app_package_name_2[0],
|
||||||
|
drm_cert_serial_number_app_1_origin_2[0]);
|
||||||
|
ASSERT_NE(drm_cert_serial_number_app_1_origin_1[0],
|
||||||
|
drm_cert_serial_number_app_1_origin_2[0]);
|
||||||
|
}
|
||||||
|
} // namespace wvcdm
|
||||||
@@ -62,17 +62,16 @@ void ProvisioningHolder::Provision(CdmCertificateType cert_type,
|
|||||||
url_request.PostCertRequestInQueryString(request);
|
url_request.PostCertRequestInQueryString(request);
|
||||||
|
|
||||||
// Receive and parse response.
|
// Receive and parse response.
|
||||||
std::string response;
|
ASSERT_NO_FATAL_FAILURE(url_request.AssertOkResponse(&response_))
|
||||||
ASSERT_NO_FATAL_FAILURE(url_request.AssertOkResponse(&response))
|
|
||||||
<< "Failed to fetch provisioning response. "
|
<< "Failed to fetch provisioning response. "
|
||||||
<< DumpProvAttempt(request, response, cert_type);
|
<< DumpProvAttempt(request, response_, cert_type);
|
||||||
|
|
||||||
if (binary_provisioning) {
|
if (binary_provisioning) {
|
||||||
// extract provisioning response from received message
|
// extract provisioning response from received message
|
||||||
// Extracts signed response from JSON string, result is serialized
|
// Extracts signed response from JSON string, result is serialized
|
||||||
// protobuf.
|
// protobuf.
|
||||||
std::string protobuf_response;
|
std::string protobuf_response;
|
||||||
const bool extract_ok = ExtractSignedMessage(response, &protobuf_response);
|
const bool extract_ok = ExtractSignedMessage(response_, &protobuf_response);
|
||||||
ASSERT_TRUE(extract_ok) << "Failed to extract signed serialized "
|
ASSERT_TRUE(extract_ok) << "Failed to extract signed serialized "
|
||||||
"response from JSON response";
|
"response from JSON response";
|
||||||
LOGV("Extracted response message: \n%s\n", protobuf_response.c_str());
|
LOGV("Extracted response message: \n%s\n", protobuf_response.c_str());
|
||||||
@@ -87,21 +86,15 @@ void ProvisioningHolder::Provision(CdmCertificateType cert_type,
|
|||||||
<< "Failed to decode base64 of response: response = "
|
<< "Failed to decode base64 of response: response = "
|
||||||
<< protobuf_response;
|
<< protobuf_response;
|
||||||
|
|
||||||
const std::string binary_protobuf_response(response_vec.begin(),
|
response_.assign(response_vec.begin(), response_vec.end());
|
||||||
response_vec.end());
|
|
||||||
|
|
||||||
ASSERT_EQ(NO_ERROR, cdm_engine_->HandleProvisioningResponse(
|
|
||||||
binary_protobuf_response, kLevelDefault,
|
|
||||||
&certificate_, &wrapped_key_))
|
|
||||||
<< "Binary provisioning failed. "
|
|
||||||
<< DumpProvAttempt(request, response, cert_type);
|
|
||||||
} else {
|
|
||||||
ASSERT_EQ(NO_ERROR,
|
|
||||||
cdm_engine_->HandleProvisioningResponse(
|
|
||||||
response, kLevelDefault, &certificate_, &wrapped_key_))
|
|
||||||
<< "Non-binary provisioning failed. "
|
|
||||||
<< DumpProvAttempt(request, response, cert_type);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ASSERT_EQ(NO_ERROR,
|
||||||
|
cdm_engine_->HandleProvisioningResponse(
|
||||||
|
response_, kLevelDefault, &certificate_, &wrapped_key_))
|
||||||
|
<< (binary_provisioning ? "Binary provisioning failed. "
|
||||||
|
: "Non-binary provisioning failed. ")
|
||||||
|
<< DumpProvAttempt(request, response_, cert_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ProvisioningHolder::ExtractSignedMessage(const std::string& response,
|
bool ProvisioningHolder::ExtractSignedMessage(const std::string& response,
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ class ProvisioningHolder {
|
|||||||
provisioning_server_url_(provisioning_server_url),
|
provisioning_server_url_(provisioning_server_url),
|
||||||
provisioning_service_certificate_(provisioning_service_certificate) {}
|
provisioning_service_certificate_(provisioning_service_certificate) {}
|
||||||
void Provision(CdmCertificateType cert_type, bool binary_provisioning);
|
void Provision(CdmCertificateType cert_type, bool binary_provisioning);
|
||||||
|
void Provision(bool binary_provisioning) {
|
||||||
|
Provision(kCertificateWidevine, binary_provisioning);
|
||||||
|
}
|
||||||
|
std::string response() const { return response_; }
|
||||||
std::string certificate() const { return certificate_; }
|
std::string certificate() const { return certificate_; }
|
||||||
std::string wrapped_key() const { return wrapped_key_; }
|
std::string wrapped_key() const { return wrapped_key_; }
|
||||||
|
|
||||||
@@ -27,6 +31,7 @@ class ProvisioningHolder {
|
|||||||
TestCdmEngine* cdm_engine_;
|
TestCdmEngine* cdm_engine_;
|
||||||
std::string provisioning_server_url_;
|
std::string provisioning_server_url_;
|
||||||
std::string provisioning_service_certificate_;
|
std::string provisioning_service_certificate_;
|
||||||
|
std::string response_;
|
||||||
std::string certificate_;
|
std::string certificate_;
|
||||||
std::string wrapped_key_;
|
std::string wrapped_key_;
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,11 @@ include $(LOCAL_PATH)/integration-test.mk
|
|||||||
# coverage-test.mk will define test name.
|
# coverage-test.mk will define test name.
|
||||||
include $(LOCAL_PATH)/coverage-test.mk
|
include $(LOCAL_PATH)/coverage-test.mk
|
||||||
|
|
||||||
|
test_name := core_integration_test
|
||||||
|
test_src_dir := ../core/test
|
||||||
|
test_main := ../core/test/test_main.cpp
|
||||||
|
include $(LOCAL_PATH)/integration-test.mk
|
||||||
|
|
||||||
test_name := counter_metric_unittest
|
test_name := counter_metric_unittest
|
||||||
test_src_dir := ../metrics/test
|
test_src_dir := ../metrics/test
|
||||||
include $(LOCAL_PATH)/unit-test.mk
|
include $(LOCAL_PATH)/unit-test.mk
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ adb_shell_run cdm_engine_test
|
|||||||
adb_shell_run cdm_session_unittest
|
adb_shell_run cdm_session_unittest
|
||||||
adb_shell_run cdm_usage_table_unittest
|
adb_shell_run cdm_usage_table_unittest
|
||||||
adb_shell_run certificate_provisioning_unittest
|
adb_shell_run certificate_provisioning_unittest
|
||||||
|
adb_shell_run core_integration_test
|
||||||
adb_shell_run counter_metric_unittest
|
adb_shell_run counter_metric_unittest
|
||||||
adb_shell_run crypto_session_unittest
|
adb_shell_run crypto_session_unittest
|
||||||
adb_shell_run device_files_unittest
|
adb_shell_run device_files_unittest
|
||||||
@@ -128,3 +129,4 @@ adb_shell_run system_id_extractor_unittest
|
|||||||
adb_shell_run timer_unittest
|
adb_shell_run timer_unittest
|
||||||
adb_shell_run value_metric_unittest
|
adb_shell_run value_metric_unittest
|
||||||
adb_shell_run wv_cdm_metrics_test
|
adb_shell_run wv_cdm_metrics_test
|
||||||
|
adb_shell_run wv_plugin_test $PROVISIONING_ARG
|
||||||
|
|||||||
76
libwvdrmengine/test/Android.bp
Normal file
76
libwvdrmengine/test/Android.bp
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
// Copyright 2020 Google LLC. All rights reserved. This file and proprietary
|
||||||
|
// source code may only be used and distributed under the Widevine
|
||||||
|
// License Agreement.
|
||||||
|
|
||||||
|
// *** THIS PACKAGE HAS SPECIAL LICENSING CONDITIONS. PLEASE
|
||||||
|
// CONSULT THE OWNERS AND opensource-licensing@google.com BEFORE
|
||||||
|
// DEPENDING ON IT IN YOUR PROJECT. ***
|
||||||
|
package {
|
||||||
|
// See: http://go/android-license-faq
|
||||||
|
// A large-scale-change added 'default_applicable_licenses' to import
|
||||||
|
// all of the 'license_kinds' from "vendor_widevine_license"
|
||||||
|
// to get the below license kinds:
|
||||||
|
// legacy_by_exception_only (by exception only)
|
||||||
|
// legacy_proprietary (by exception only)
|
||||||
|
default_applicable_licenses: ["vendor_widevine_license"],
|
||||||
|
}
|
||||||
|
|
||||||
|
cc_test {
|
||||||
|
name: "wv_plugin_test",
|
||||||
|
test_suites: ["device-tests"],
|
||||||
|
gtest: true,
|
||||||
|
|
||||||
|
srcs: [
|
||||||
|
"WVPlugin_test.cpp",
|
||||||
|
":libwvdrmengine_src",
|
||||||
|
":vts_cdm_core_test_srcs",
|
||||||
|
],
|
||||||
|
include_dirs: [
|
||||||
|
"frameworks/av/include",
|
||||||
|
"frameworks/native/include",
|
||||||
|
"vendor/widevine/libwvdrmengine/cdm/core/include",
|
||||||
|
"vendor/widevine/libwvdrmengine/cdm/core/test",
|
||||||
|
"vendor/widevine/libwvdrmengine/cdm/include",
|
||||||
|
"vendor/widevine/libwvdrmengine/cdm/metrics/include/",
|
||||||
|
"vendor/widevine/libwvdrmengine/cdm/util/include",
|
||||||
|
"vendor/widevine/libwvdrmengine/include",
|
||||||
|
"vendor/widevine/libwvdrmengine/mediadrm/include",
|
||||||
|
"vendor/widevine/libwvdrmengine/oemcrypto/include",
|
||||||
|
],
|
||||||
|
header_libs: [
|
||||||
|
"libstagefright_foundation_headers",
|
||||||
|
"libutils_headers",
|
||||||
|
],
|
||||||
|
static_libs: [
|
||||||
|
"libcdm",
|
||||||
|
"libcdm_protos",
|
||||||
|
"libcdm_utils",
|
||||||
|
"libgmock",
|
||||||
|
"libgtest",
|
||||||
|
"libgtest_main",
|
||||||
|
"libjsmn",
|
||||||
|
"libjsoncpp",
|
||||||
|
"libprotobuf-cpp-lite",
|
||||||
|
"libutils",
|
||||||
|
"libwv_odk",
|
||||||
|
"libwvdrmdrmplugin_aidl",
|
||||||
|
"libwvlevel3",
|
||||||
|
],
|
||||||
|
shared_libs: [
|
||||||
|
"android.hardware.drm-V1-ndk",
|
||||||
|
"libbase",
|
||||||
|
"libbinder_ndk",
|
||||||
|
"libcrypto",
|
||||||
|
"libdl",
|
||||||
|
"liblog",
|
||||||
|
"libssl",
|
||||||
|
],
|
||||||
|
cflags: [
|
||||||
|
"-Wall",
|
||||||
|
"-Werror",
|
||||||
|
"-Wextra",
|
||||||
|
],
|
||||||
|
|
||||||
|
proprietary: true,
|
||||||
|
owner: "widevine",
|
||||||
|
}
|
||||||
312
libwvdrmengine/test/WVPlugin_test.cpp
Normal file
312
libwvdrmengine/test/WVPlugin_test.cpp
Normal file
@@ -0,0 +1,312 @@
|
|||||||
|
// 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 "utils/StrongPointer.h"
|
||||||
|
#include <utils/Log.h>
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "WVCDMSingleton.h"
|
||||||
|
#include "WVDrmPlugin.h"
|
||||||
|
#include "WVGenericCryptoInterface.h"
|
||||||
|
#include "certificate_provisioning.h"
|
||||||
|
#include "gmock/gmock.h"
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
#include "string_conversions.h"
|
||||||
|
#include "url_request.h"
|
||||||
|
#include "wv_cdm_constants.h"
|
||||||
|
#include "wv_cdm_types.h"
|
||||||
|
#include "wv_content_decryption_module.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
static const std::string kDeviceUniqueId = "deviceUniqueId";
|
||||||
|
static const std::string kSecurityLevel = "securityLevel";
|
||||||
|
static const std::string kSystemId = "systemId";
|
||||||
|
static const std::string kTestAppPackageName = "test_app_package";
|
||||||
|
static const std::string kTestAppPackageName1 = "test_app_package_1";
|
||||||
|
static const std::string kTestAppPackageName2 = "test_app_package_2";
|
||||||
|
static const std::string kTestAppPackageName3 = "test_app_package_3";
|
||||||
|
static const char* const kUnprovisionResponse = "unprovision";
|
||||||
|
|
||||||
|
// HTTP response codes.
|
||||||
|
const int kHttpOk = 200;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace wvdrm {
|
||||||
|
namespace hardware {
|
||||||
|
namespace drm {
|
||||||
|
namespace widevine {
|
||||||
|
|
||||||
|
class WVPluginTest : public testing::Test {
|
||||||
|
|
||||||
|
public :
|
||||||
|
virtual void SetUp() {
|
||||||
|
mDrmPlugin = ::ndk::SharedRefBase::make<WVDrmPlugin>(
|
||||||
|
getCDM(), kTestAppPackageName, &mOemCryptoInterface, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<WVDrmPlugin> createDrmPlugin(
|
||||||
|
const std::string& app_package_name) {
|
||||||
|
return ::ndk::SharedRefBase::make<WVDrmPlugin>(
|
||||||
|
getCDM(), app_package_name, &mOemCryptoInterface, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will create a provisioning request, send it to the service and
|
||||||
|
// process the response. The provisioning response will be passed back
|
||||||
|
// if needed for inspection. If first stage (for provisioning 4.0)
|
||||||
|
// or OKP is needed, the first roundtrip will be completed
|
||||||
|
// silently. The method will return after the second round trip.
|
||||||
|
// Verify the return value in case of errors.
|
||||||
|
bool ensureProvisioned(
|
||||||
|
std::shared_ptr<WVDrmPlugin>& plugin,
|
||||||
|
wvcdm::CdmProvisioningResponse* provisioning_response) {
|
||||||
|
::aidl::android::hardware::drm::ProvisionRequest request;
|
||||||
|
|
||||||
|
std::string value;
|
||||||
|
auto status =
|
||||||
|
mDrmPlugin->getPropertyString(kSystemId, &value);
|
||||||
|
EXPECT_TRUE(status.isOk()) << "Failed to get System Id";
|
||||||
|
|
||||||
|
long val = std::strtol(value.c_str(), nullptr, 10);
|
||||||
|
EXPECT_NE(val, 0) << "System ID invalid: " << errno;
|
||||||
|
EXPECT_NE(val, LONG_MIN) << "System ID invalid: " << errno;
|
||||||
|
EXPECT_NE(val, LONG_MAX) << "System ID invalid: " << errno;
|
||||||
|
|
||||||
|
uint32_t system_id = val;
|
||||||
|
|
||||||
|
if (system_id == wvcdm::NULL_SYSTEM_ID) {
|
||||||
|
// Either first stage prov 4.0 or OKP
|
||||||
|
bool result = provision(plugin, provisioning_response);
|
||||||
|
if (!result)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return provision(plugin, provisioning_response);
|
||||||
|
}
|
||||||
|
|
||||||
|
void unprovision(const std::shared_ptr<WVDrmPlugin>& plugin) {
|
||||||
|
std::vector<uint8_t> unprovisionResponse;
|
||||||
|
unprovisionResponse
|
||||||
|
.assign(kUnprovisionResponse,
|
||||||
|
kUnprovisionResponse + strlen(kUnprovisionResponse));
|
||||||
|
::aidl::android::hardware::drm::ProvideProvisionResponseResult result;
|
||||||
|
auto status =
|
||||||
|
plugin->provideProvisionResponse(unprovisionResponse, &result);
|
||||||
|
ASSERT_TRUE(status.isOk()) << "Failed to unprovision";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isL1() {
|
||||||
|
std::string security_level;
|
||||||
|
auto status =
|
||||||
|
mDrmPlugin->getPropertyString(kSecurityLevel, &security_level);
|
||||||
|
EXPECT_TRUE(status.isOk()) << "Failed to get Security level";
|
||||||
|
return security_level == wvcdm::QUERY_VALUE_SECURITY_LEVEL_L1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string getDeviceUniqueId(std::shared_ptr<WVDrmPlugin>& plugin) {
|
||||||
|
std::vector<uint8_t> device_unique_id;
|
||||||
|
auto status =
|
||||||
|
plugin->getPropertyByteArray(kDeviceUniqueId, &device_unique_id);
|
||||||
|
EXPECT_TRUE(status.isOk()) << "Failed to get Device Unique ID";
|
||||||
|
return std::string(device_unique_id.begin(), device_unique_id.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool getSerialNumber(const wvcdm::CdmProvisioningResponse& response,
|
||||||
|
std::string* serial_number) {
|
||||||
|
if (serial_number == nullptr) {
|
||||||
|
ADD_FAILURE();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
wvcdm::CdmProvisioningResponse provisioning_response;
|
||||||
|
if (!wvcdm::CertificateProvisioning::ExtractAndDecodeSignedMessage(
|
||||||
|
response, &provisioning_response)) {
|
||||||
|
ADD_FAILURE();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
video_widevine::SignedProvisioningMessage signed_response;
|
||||||
|
if (!signed_response.ParseFromString(provisioning_response) ||
|
||||||
|
!signed_response.has_message()) {
|
||||||
|
ADD_FAILURE();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
video_widevine::ProvisioningResponse prov_response;
|
||||||
|
if (!prov_response.ParseFromString(signed_response.message()) ||
|
||||||
|
!prov_response.has_device_certificate()) {
|
||||||
|
ADD_FAILURE();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!wvcdm::CertificateProvisioning::ExtractDeviceInfo(
|
||||||
|
prov_response.device_certificate(), serial_number, nullptr, nullptr,
|
||||||
|
nullptr)) {
|
||||||
|
ADD_FAILURE();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Create a Provisioning request, send it to the service and process the
|
||||||
|
// response. Pass back the provisioning response if needed for inspection
|
||||||
|
bool provision(
|
||||||
|
std::shared_ptr<WVDrmPlugin>& plugin,
|
||||||
|
wvcdm::CdmProvisioningResponse* provisioning_response) {
|
||||||
|
|
||||||
|
::aidl::android::hardware::drm::ProvisionRequest request;
|
||||||
|
auto status =
|
||||||
|
plugin->getProvisionRequest(std::string(""), std::string(""), &request);
|
||||||
|
EXPECT_TRUE(status.isOk()) << "Failed to get Provisioning request";
|
||||||
|
if (!status.isOk()) return false;
|
||||||
|
|
||||||
|
wvcdm::CdmProvisioningRequest
|
||||||
|
provisioning_request(request.request.begin(), request.request.end());
|
||||||
|
bool sts = GetProvisioningResponse(provisioning_request,
|
||||||
|
request.defaultUrl,
|
||||||
|
provisioning_response);
|
||||||
|
if (!sts) {
|
||||||
|
EXPECT_TRUE(sts) << "Failed to get provisioning response";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
wvcdm::CdmProvisioningResponse prov_response;
|
||||||
|
|
||||||
|
std::vector<uint8_t> response(provisioning_response->begin(),
|
||||||
|
provisioning_response->end());
|
||||||
|
::aidl::android::hardware::drm::ProvideProvisionResponseResult result;
|
||||||
|
status = plugin->provideProvisionResponse(response, &result);
|
||||||
|
EXPECT_TRUE(status.isOk()) << "Failed to process provisioning response";
|
||||||
|
if (!status.isOk()) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post a request and extract the signed provisioning message from
|
||||||
|
// the HTTP response.
|
||||||
|
// Setting a non-zero value to |duration_seconds| will request a
|
||||||
|
// limited duration DRM certificate.
|
||||||
|
bool GetProvisioningResponse(const wvcdm::CdmProvisioningRequest& request,
|
||||||
|
const std::string& url,
|
||||||
|
wvcdm::CdmProvisioningResponse* response) {
|
||||||
|
EXPECT_NE(response, nullptr) << "No |response| parameter provided";
|
||||||
|
if (response == nullptr)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Use secure connection and chunk transfer coding.
|
||||||
|
wvcdm::UrlRequest url_request(url);
|
||||||
|
EXPECT_TRUE(url_request.is_connected()) << "Fail to connect to " << url;
|
||||||
|
url_request.PostCertRequestInQueryString(request);
|
||||||
|
EXPECT_TRUE(url_request.GetResponse(response));
|
||||||
|
|
||||||
|
int http_status_code = url_request.GetStatusCode(*response);
|
||||||
|
EXPECT_EQ(kHttpOk, http_status_code) << "provisioning_response = "
|
||||||
|
<< *response;
|
||||||
|
if (kHttpOk != http_status_code) {
|
||||||
|
LogResponseError(*response, http_status_code);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogResponseError(const std::string& message, int http_status_code) {
|
||||||
|
LOGD("HTTP Status code = %d", http_status_code);
|
||||||
|
LOGD("HTTP response(%zu): %s", message.size(),
|
||||||
|
wvutil::b2a_hex(message).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<WVDrmPlugin> mDrmPlugin;
|
||||||
|
wvdrm::WVGenericCryptoInterface mOemCryptoInterface;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Verify that SPOIDs and DRM certificate serial number are stable between
|
||||||
|
// factory resets/provisioning attempts for the same app and different between
|
||||||
|
// different apps. Test using three different apps.
|
||||||
|
TEST_F(WVPluginTest, ProvisioningStableSpoidTestL1) {
|
||||||
|
std::shared_ptr<WVDrmPlugin> drm_plugin1 =
|
||||||
|
createDrmPlugin(kTestAppPackageName1);
|
||||||
|
|
||||||
|
if (!isL1()) // declare success if device does not natively support L1
|
||||||
|
return;
|
||||||
|
|
||||||
|
wvcdm::CdmProvisioningResponse provisioning_response;
|
||||||
|
|
||||||
|
unprovision(drm_plugin1);
|
||||||
|
std::string spoid1_1 = getDeviceUniqueId(drm_plugin1);
|
||||||
|
ASSERT_TRUE(ensureProvisioned(drm_plugin1, &provisioning_response));
|
||||||
|
std::string drm_cert_serial_number1_1;
|
||||||
|
ASSERT_TRUE(getSerialNumber(provisioning_response,
|
||||||
|
&drm_cert_serial_number1_1));
|
||||||
|
|
||||||
|
unprovision(drm_plugin1);
|
||||||
|
std::string spoid1_2 = getDeviceUniqueId(drm_plugin1);
|
||||||
|
ASSERT_TRUE(ensureProvisioned(drm_plugin1, &provisioning_response));
|
||||||
|
std::string drm_cert_serial_number1_2;
|
||||||
|
ASSERT_TRUE(getSerialNumber(provisioning_response,
|
||||||
|
&drm_cert_serial_number1_2));
|
||||||
|
|
||||||
|
std::shared_ptr<WVDrmPlugin> drm_plugin2 =
|
||||||
|
createDrmPlugin(kTestAppPackageName2);
|
||||||
|
|
||||||
|
unprovision(drm_plugin2);
|
||||||
|
std::string spoid2_1 = getDeviceUniqueId(drm_plugin2);
|
||||||
|
ASSERT_TRUE(ensureProvisioned(drm_plugin2, &provisioning_response));
|
||||||
|
std::string drm_cert_serial_number2_1;
|
||||||
|
ASSERT_TRUE(getSerialNumber(provisioning_response,
|
||||||
|
&drm_cert_serial_number2_1));
|
||||||
|
|
||||||
|
unprovision(drm_plugin2);
|
||||||
|
std::string spoid2_2 = getDeviceUniqueId(drm_plugin2);
|
||||||
|
ASSERT_TRUE(ensureProvisioned(drm_plugin2, &provisioning_response));
|
||||||
|
std::string drm_cert_serial_number2_2;
|
||||||
|
ASSERT_TRUE(getSerialNumber(provisioning_response,
|
||||||
|
&drm_cert_serial_number2_2));
|
||||||
|
|
||||||
|
std::shared_ptr<WVDrmPlugin> drm_plugin3 =
|
||||||
|
createDrmPlugin(kTestAppPackageName3);
|
||||||
|
|
||||||
|
unprovision(drm_plugin3);
|
||||||
|
std::string spoid3_1 = getDeviceUniqueId(drm_plugin3);
|
||||||
|
ASSERT_TRUE(ensureProvisioned(drm_plugin3, &provisioning_response));
|
||||||
|
std::string drm_cert_serial_number3_1;
|
||||||
|
ASSERT_TRUE(getSerialNumber(provisioning_response,
|
||||||
|
&drm_cert_serial_number3_1));
|
||||||
|
|
||||||
|
unprovision(drm_plugin3);
|
||||||
|
std::string spoid3_2 = getDeviceUniqueId(drm_plugin3);
|
||||||
|
ASSERT_TRUE(ensureProvisioned(drm_plugin3, &provisioning_response));
|
||||||
|
std::string drm_cert_serial_number3_2;
|
||||||
|
ASSERT_TRUE(getSerialNumber(provisioning_response,
|
||||||
|
&drm_cert_serial_number3_2));
|
||||||
|
|
||||||
|
// Verify that the spoids are stable after a reprovision
|
||||||
|
EXPECT_EQ(spoid1_1, spoid1_2);
|
||||||
|
EXPECT_EQ(spoid2_1, spoid2_2);
|
||||||
|
EXPECT_EQ(spoid3_1, spoid3_2);
|
||||||
|
|
||||||
|
// Verify that the spoids are different for different apps
|
||||||
|
EXPECT_NE(spoid1_1, spoid2_1);
|
||||||
|
EXPECT_NE(spoid1_1, spoid3_1);
|
||||||
|
EXPECT_NE(spoid2_1, spoid3_1);
|
||||||
|
|
||||||
|
// Verify that the DRM certificate serial numbers are stable after a
|
||||||
|
// reprovision
|
||||||
|
EXPECT_EQ(drm_cert_serial_number1_1, drm_cert_serial_number1_2);
|
||||||
|
EXPECT_EQ(drm_cert_serial_number2_1, drm_cert_serial_number2_2);
|
||||||
|
EXPECT_EQ(drm_cert_serial_number3_1, drm_cert_serial_number3_2);
|
||||||
|
|
||||||
|
// Verify that the DRM certificate serial numbers are different for different
|
||||||
|
// apps
|
||||||
|
EXPECT_NE(drm_cert_serial_number1_1, drm_cert_serial_number2_1);
|
||||||
|
EXPECT_NE(drm_cert_serial_number1_1, drm_cert_serial_number3_1);
|
||||||
|
EXPECT_NE(drm_cert_serial_number2_1, drm_cert_serial_number3_1);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace widevine
|
||||||
|
} // namespace drm
|
||||||
|
} // namespace hardware
|
||||||
|
} // namespace wvdrm
|
||||||
Reference in New Issue
Block a user