Files
android/libwvdrmengine/cdm/core/test/license_holder.cpp
Vicky Min e642847b81 Update license holder to handle Android license releases
Since the CDM engine handles license releases for CE CDM and Android
differently, this changes the license release test to accomodate for
that.

Bug: 348712053
Change-Id: Ibc768e5d5c31ef8c2226b63dc622ffabfc0591fe
2024-09-04 19:33:16 +00:00

320 lines
12 KiB
C++

// Copyright 2022 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine License
// Agreement.
#include "license_holder.h"
#include "license_request.h"
#include "message_dumper.h"
#include "oec_device_features.h"
#include "properties.h"
#include "test_base.h"
namespace wvcdm {
namespace {
const std::string kCencMimeType = "cenc";
} // namespace
LicenseHolder::~LicenseHolder() {}
void LicenseHolder::OpenSession() {
CdmResponseType status = cdm_engine_->OpenSession(
config_.key_system(), nullptr, &event_listener_, &session_id_);
ASSERT_EQ(NO_ERROR, status);
ASSERT_TRUE(cdm_engine_->IsOpenSession(session_id_));
}
void LicenseHolder::CloseSession() {
CdmResponseType status = cdm_engine_->CloseSession(session_id_);
ASSERT_EQ(NO_ERROR, status);
ASSERT_FALSE(cdm_engine_->IsOpenSession(session_id_));
}
void LicenseHolder::FetchLicense() {
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();
CdmKeyRequest key_request;
ASSERT_NO_FATAL_FAILURE(GenerateKeyRequest(init_data, &key_request))
<< "Failed for " << content_id();
if (config_.dump_golden_data()) {
MessageDumper::DumpLicenseRequest(key_request);
}
ASSERT_NO_FATAL_FAILURE(GetKeyResponse(key_request))
<< "Failed for " << content_id();
if (config_.dump_golden_data()) {
MessageDumper::DumpLicense(key_response_);
}
}
void LicenseHolder::LoadLicense() {
CdmLicenseType license_type;
ASSERT_EQ(KEY_ADDED, cdm_engine_->AddKey(session_id_, key_response_,
&license_type, &key_set_id_))
<< "Failed to load license for " << content_id();
if (can_persist_) {
ASSERT_EQ(license_type, kLicenseTypeOffline)
<< "Failed for " << content_id();
} else {
ASSERT_EQ(license_type, kLicenseTypeStreaming)
<< "Failed for " << content_id();
}
}
void LicenseHolder::FailLoadLicense() {
CdmLicenseType license_type;
ASSERT_NE(KEY_ADDED, cdm_engine_->AddKey(session_id_, key_response_,
&license_type, &key_set_id_))
<< "Unexpected success loading license for " << content_id();
}
void LicenseHolder::ReloadLicense() {
CdmResponseType status = cdm_engine_->RestoreKey(session_id_, key_set_id_);
ASSERT_EQ(KEY_ADDED, status)
<< "Failed to reload license for " << content_id();
}
void LicenseHolder::FailReloadLicense() {
const CdmResponseType status =
cdm_engine_->RestoreKey(session_id_, key_set_id_);
ASSERT_NE(KEY_ADDED, status)
<< "Unexpected success loading license for " << content_id();
}
void LicenseHolder::GenerateAndPostRenewalRequest(
const std::string& renewal_policy_id) {
event_listener_.set_renewal_needed(false);
CdmKeyRequest request;
const CdmResponseType result =
cdm_engine_->GenerateRenewalRequest(session_id_, &request);
ASSERT_EQ(KEY_MESSAGE, result) << "Failed for " << content_id();
if (config_.dump_golden_data()) {
MessageDumper::DumpRenewalRequest(request);
}
const std::string url = MakeUrl(config_.renewal_server(), renewal_policy_id);
request_in_flight_.reset(new UrlRequest(url));
ASSERT_TRUE(request_in_flight_->is_connected())
<< "Failed for " << content_id();
request_in_flight_->PostRequest(request.message);
}
void LicenseHolder::FetchRenewal() {
ASSERT_NE(request_in_flight_, nullptr) << "Failed for " << content_id();
ASSERT_NO_FATAL_FAILURE(
request_in_flight_->AssertOkResponse(&request_response_))
<< "Renewal failed for " << content_id();
}
void LicenseHolder::LoadRenewal() {
LicenseRequest license_request;
license_request.GetDrmMessage(request_response_, request_message_);
if (config_.dump_golden_data()) {
MessageDumper::DumpRenewal(request_message_);
}
EXPECT_EQ(KEY_ADDED, cdm_engine_->RenewKey(session_id_, request_message_))
<< "Failed for " << content_id();
}
void LicenseHolder::GenerateAndPostReleaseRequest(
const std::string& release_policy_id) {
event_listener_.set_renewal_needed(false);
CdmKeyRequest request;
CdmAppParameterMap empty_app_parameters;
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();
CdmSessionId session_id;
CdmKeySetId key_set_id;
CdmResponseType result;
// For Android when key set IDs are used, the key set ID passed in should have
// a value and the session ID should be empty.
if (!Properties::AlwaysUseKeySetIds()) {
key_set_id = key_set_id_;
result = cdm_engine_->OpenKeySetSession(key_set_id_, nullptr, nullptr);
ASSERT_EQ(NO_ERROR, result) << "Failed for " << content_id();
// For CE CDM, we only need the session ID to be valid.
} else {
session_id = session_id_;
}
result = cdm_engine_->GenerateKeyRequest(session_id, key_set_id, init_data,
kLicenseTypeRelease,
empty_app_parameters, &request);
ASSERT_EQ(KEY_MESSAGE, result) << "Failed for " << content_id();
if (!Properties::AlwaysUseKeySetIds()) {
cdm_engine_->CloseKeySetSession(key_set_id_);
}
if (config_.dump_golden_data()) {
// TODO (b/295956275) vickymin: write DumpReleaseRequest function
// MessageDumper::DumpReleaseRequest(request);
}
const std::string url = MakeUrl(config_.renewal_server(), release_policy_id);
request_in_flight_.reset(new UrlRequest(url));
ASSERT_TRUE(request_in_flight_->is_connected())
<< "Failed for " << content_id();
request_in_flight_->PostRequest(request.message);
}
void LicenseHolder::FetchRelease() {
ASSERT_NE(request_in_flight_, nullptr) << "Failed for " << content_id();
ASSERT_NO_FATAL_FAILURE(
request_in_flight_->AssertOkResponse(&request_response_))
<< "Renewal failed for " << content_id();
}
void LicenseHolder::LoadRelease() {
LicenseRequest license_request;
license_request.GetDrmMessage(request_response_, request_message_);
if (config_.dump_golden_data()) {
// TODO (b/295956275) vickymin: write DumpRelease function
// MessageDumper::DumpRelease(request_message_);
}
}
void LicenseHolder::RemoveLicense() {
EXPECT_EQ(NO_ERROR, cdm_engine_->RemoveLicense(session_id_))
<< "Failed for " << content_id();
}
CdmResponseType LicenseHolder::Decrypt(const std::string& key_id) {
constexpr size_t buffer_size = 500;
const std::vector<uint8_t> input(buffer_size, 0);
std::vector<uint8_t> output(buffer_size, 0);
const std::vector<uint8_t> iv(KEY_IV_SIZE, 0);
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);
return cdm_engine_->DecryptV16(session_id_, params);
}
CdmResponseType LicenseHolder::DecryptClearLead(const std::string& key_id) {
constexpr size_t buffer_size = 500;
const std::vector<uint8_t> input(buffer_size, 0);
std::vector<uint8_t> output(buffer_size, 0);
const std::vector<uint8_t> iv(KEY_IV_SIZE, 0);
CdmDecryptionParametersV16 params(key_id);
params.is_secure = false;
CdmDecryptionSample sample1(input.data(), output.data(), 0, input.size(), iv);
CdmDecryptionSample sample2(input.data(), output.data(), 0, input.size(), iv);
CdmDecryptionSubsample subsample(input.size(), 0);
sample1.subsamples.push_back(subsample);
sample2.subsamples.push_back(subsample);
params.samples.push_back(sample1);
params.samples.push_back(sample2);
return cdm_engine_->DecryptV16(session_id_, params);
}
void LicenseHolder::DecryptSecure(const KeyId& key_id) {
ASSERT_TRUE(wvoec::global_features.test_secure_buffers);
constexpr size_t buffer_size = 500;
const std::vector<uint8_t> input(buffer_size, 0);
const std::vector<uint8_t> iv(KEY_IV_SIZE, 0);
// To create a secure buffer, we need to know the OEMCrypto session id.
CdmQueryMap query_map;
cdm_engine_->QueryOemCryptoSessionId(session_id_, &query_map);
const std::string oec_session_id_string =
query_map[QUERY_KEY_OEMCRYPTO_SESSION_ID];
uint32_t oec_session_id = std::stoi(oec_session_id_string);
int secure_buffer_fid;
OEMCrypto_DestBufferDesc output_descriptor;
output_descriptor.type = OEMCrypto_BufferType_Secure;
output_descriptor.buffer.secure.secure_buffer_length = buffer_size;
ASSERT_EQ(
OEMCrypto_AllocateSecureBuffer(oec_session_id, buffer_size,
&output_descriptor, &secure_buffer_fid),
OEMCrypto_SUCCESS);
ASSERT_NE(output_descriptor.buffer.secure.secure_buffer, nullptr);
// It is OK if OEMCrypto changes the maximum size, but there must
// still be enough room for our data.
ASSERT_GE(output_descriptor.buffer.secure.secure_buffer_length, buffer_size);
output_descriptor.buffer.secure.offset = 0;
// Now create a sample array for the CDM layer.
CdmDecryptionParametersV16 params(key_id);
params.is_secure = true;
CdmDecryptionSample sample(input.data(),
output_descriptor.buffer.secure.secure_buffer, 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);
// Free the secure buffer before we check the return status.
EXPECT_EQ(OEMCrypto_FreeSecureBuffer(oec_session_id, &output_descriptor,
secure_buffer_fid),
OEMCrypto_SUCCESS);
ASSERT_EQ(status, NO_ERROR);
}
void LicenseHolder::FailDecrypt(const KeyId& key_id,
CdmResponseEnum expected_status) {
CdmResponseType status = Decrypt(key_id);
// If the server knows we cannot handle the key, it would not have given us
// the key. In that case, the status should indicate no key.
if (status != NEED_KEY) {
// Otherwise, we should have gotten the expected error.
ASSERT_EQ(expected_status, status);
}
}
void LicenseHolder::GenerateKeyRequest(const InitializationData& init_data,
CdmKeyRequest* key_request) {
CdmAppParameterMap empty_app_parameters;
CdmKeySetId empty_key_set_id;
CdmLicenseType license_type =
can_persist_ ? kLicenseTypeOffline : kLicenseTypeStreaming;
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);
}
std::string LicenseHolder::MakeUrl(const std::string& server_url,
const std::string& renewal_policy_id) {
// For tests, we want to specify the policy, but the UAT server only allows us
// to set the content id as the video_id. So each policy is matched to a
// single license with the same name. The local license server, on the other
// hand, wants to see the policy id in the url. So we have to guess which
// format to use based on the name of the server.
const std::string path = server_url + config_.client_auth();
if (!renewal_policy_id.empty()) {
const std::string video_query =
"video_id=" + content_id() + "&renewal_policy=" + renewal_policy_id;
// If there is already a parameter, then we don't need to add another
// question mark.
return path + ((path.find('?') == std::string::npos) ? '?' : '&') +
video_query;
} else {
return path;
}
}
void LicenseHolder::GetKeyResponse(const CdmKeyRequest& key_request) {
// The content id matches the policy id used on UAT.
const std::string url = MakeUrl(config_.license_server(), "");
UrlRequest url_request(url);
ASSERT_TRUE(url_request.is_connected());
std::string http_response;
url_request.PostRequest(key_request.message);
ASSERT_NO_FATAL_FAILURE(url_request.AssertOkResponse(&http_response))
<< "Failed for " << content_id();
LicenseRequest license_request;
license_request.GetDrmMessage(http_response, key_response_);
}
} // namespace wvcdm