Merge changes from topics "presubmit-am-0d92e9728c2d40da892bd450843310cb", "presubmit-am-11f8881adcb544ca8682231293b0f1c7", "presubmit-am-30bc14671b7b4b309e57b8600f46b32b", "presubmit-am-35012549d66140dd9d446b5eedf6e274", "presubmit-am-374672395de04b7b8f697a54e16be928", "presubmit-am-48d77602d3694ced89dd6e82a89fa646", "presubmit-am-4f8d5681247e4064a298d1e5263c41be", "presubmit-am-89930436636343d5a779bc06ccc307dc", "presubmit-am-904492a27e4449e78cf21dd9f4ab8ff0", "presubmit-am-90646715a3284730bf356bb6f4634729", "presubmit-am-a1ae313a0fde4696b7fb8c4390d3a94c", "presubmit-am-ae051fae1d06485ca7f12bcf265e8328", "presubmit-am-b4e6ace5be72409aab8e328c6f2a0288", "presubmit-am-dd16b680e0454031b2213179b22df7d7", "presubmit-am-e249264532da4839841f4cab3675fa61", "presubmit-am-e3a2f43ba2f84f429536270e16d0d251", "presubmit-am-e5f2e7a319d04b89950c63471d7f2458", "presubmit-am-ea47ff378925466c8c92e2ed9b58c461", "presubmit-am-f582c497c3274c7e84606cf3da4b09df" into tm-dev
* changes: Change the signature format requirement of OEMCrypto_GenerateCertificateKeyPair Fix EnsureProvisioned for double provisioning Update fuzz tests to match output desriptor struct Use default url to inform app of prov40 stages Fix key_control_iv in OEMCrypto tests Fix jenkins/opk_optee after v17 merge Remove old test license holder Generic crypto tests: use license holder Reboot tests: verify offline license is valid after reboot Policy integration tests: use license holder Integration tests: add license holder Reboot test: Initialize fake clock Reboot test: save large files Test max number of DRM private keys Merge oemcrypto-v17 to master Update cipher mode elsewhere Fix 1 ClangTidyBuild finding: Add out of bounds testing for LoadKeys() Separate invalid session test for ReuseUsageEntry
This commit is contained in:
@@ -82,7 +82,8 @@ class CertificateProvisioning {
|
||||
const std::string& origin, const std::string& spoid,
|
||||
CdmProvisioningRequest* request, std::string* default_url);
|
||||
CdmResponseType GetProvisioning40RequestInternal(
|
||||
wvutil::FileSystem* file_system, CdmProvisioningRequest* request);
|
||||
wvutil::FileSystem* file_system, CdmProvisioningRequest* request,
|
||||
std::string* default_url);
|
||||
CdmResponseType FillEncryptedClientId(
|
||||
const std::string& client_token,
|
||||
video_widevine::ProvisioningRequest& provisioning_request);
|
||||
|
||||
@@ -26,6 +26,11 @@ const std::string kProvisioningServerUrl =
|
||||
"https://www.googleapis.com/"
|
||||
"certificateprovisioning/v1/devicecertificates/create"
|
||||
"?key=AIzaSyB-5OLKTx2iU5mko18DfdwK5611JIjbUhE";
|
||||
// In case of provisioning 4, the default url is used as a way to inform app of
|
||||
// the current provisioning stage. In the first stage, this suffix is appended
|
||||
// to kProvisioningServerUrl; in the second stage, there is no change to
|
||||
// kProvisioningServerUrl.
|
||||
const std::string kProv40FirstStageServerUrlSuffix = "&preProvisioning=true";
|
||||
|
||||
// NOTE: Provider ID = widevine.com
|
||||
const std::string kCpProductionServiceCertificate = wvutil::a2bs_hex(
|
||||
@@ -207,7 +212,7 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequestInternal(
|
||||
|
||||
if (crypto_session_->GetPreProvisionTokenType() ==
|
||||
kClientTokenBootCertChain) {
|
||||
return GetProvisioning40RequestInternal(file_system, request);
|
||||
return GetProvisioning40RequestInternal(file_system, request, default_url);
|
||||
}
|
||||
|
||||
// Prepare device provisioning request.
|
||||
@@ -298,7 +303,8 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequestInternal(
|
||||
}
|
||||
|
||||
CdmResponseType CertificateProvisioning::GetProvisioning40RequestInternal(
|
||||
wvutil::FileSystem* file_system, CdmProvisioningRequest* request) {
|
||||
wvutil::FileSystem* file_system, CdmProvisioningRequest* request,
|
||||
std::string* default_url) {
|
||||
if (!crypto_session_->IsOpen()) {
|
||||
LOGE("Crypto session is not open");
|
||||
return PROVISIONING_4_CRYPTO_SESSION_NOT_OPEN;
|
||||
@@ -333,6 +339,15 @@ CdmResponseType CertificateProvisioning::GetProvisioning40RequestInternal(
|
||||
}
|
||||
}
|
||||
|
||||
if (stored_oem_cert.empty()) {
|
||||
// This is the first stage provisioning.
|
||||
default_url->assign(kProvisioningServerUrl +
|
||||
kProv40FirstStageServerUrlSuffix);
|
||||
} else {
|
||||
// This is the second stage provisioning.
|
||||
default_url->assign(kProvisioningServerUrl);
|
||||
}
|
||||
|
||||
// If this is the first stage, |stored_oem_cert| remains empty. In this case,
|
||||
// the client identification token will be retrieved from OEMCrypto, which is
|
||||
// the BCC in this case.
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "device_files.h"
|
||||
#include "file_store.h"
|
||||
#include "initialization_data.h"
|
||||
#include "license_holder.h"
|
||||
#include "license_request.h"
|
||||
#include "log.h"
|
||||
#include "properties.h"
|
||||
@@ -387,14 +388,11 @@ TEST_F(WvCdmEngineTest, DISABLED_NormalDecryptionWebm) {
|
||||
|
||||
TEST_F(WvCdmEngineTest, LoadKey) {
|
||||
EnsureProvisioned();
|
||||
TestLicenseHolder holder(&cdm_engine_);
|
||||
holder.OpenSession(config_.key_system());
|
||||
holder.GenerateKeyRequest(binary_key_id(), ISO_BMFF_VIDEO_MIME_TYPE);
|
||||
holder.CreateDefaultLicense();
|
||||
std::vector<uint8_t> key_data(CONTENT_KEY_SIZE, '1');
|
||||
wvoec::KeyControlBlock block = {};
|
||||
holder.AddKey("key_one", key_data, block);
|
||||
holder.SignAndLoadLicense();
|
||||
LicenseHolder holder("CDM_Streaming", &cdm_engine_, config_);
|
||||
ASSERT_NO_FATAL_FAILURE(holder.OpenSession());
|
||||
ASSERT_NO_FATAL_FAILURE(holder.FetchLicense());
|
||||
ASSERT_NO_FATAL_FAILURE(holder.LoadLicense());
|
||||
ASSERT_NO_FATAL_FAILURE(holder.CloseSession());
|
||||
}
|
||||
|
||||
// This test generates a renewal and then requests the renewal using the server
|
||||
@@ -402,6 +400,7 @@ TEST_F(WvCdmEngineTest, LoadKey) {
|
||||
// skip this test when you want to set the license and renewal server on the
|
||||
// command line.
|
||||
TEST_F(WvCdmEngineTest, LicenseRenewalSpecifiedServer) {
|
||||
EnsureProvisioned();
|
||||
GenerateKeyRequest(binary_key_id(), kCencMimeType);
|
||||
VerifyNewKeyResponse(config_.license_server(), config_.client_auth());
|
||||
|
||||
@@ -423,6 +422,7 @@ TEST_F(WvCdmEngineTest, LicenseRenewalSpecifiedServer) {
|
||||
// This test generates a renewal and then requests it from the server specified
|
||||
// by the current test configuration.
|
||||
TEST_F(WvCdmEngineTest, LicenseRenewal) {
|
||||
EnsureProvisioned();
|
||||
GenerateKeyRequest(binary_key_id(), kCencMimeType);
|
||||
VerifyNewKeyResponse(config_.license_server(), config_.client_auth());
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
#include "clock.h"
|
||||
#include "config_test_env.h"
|
||||
#include "initialization_data.h"
|
||||
#include "license_request.h"
|
||||
#include "license_holder.h"
|
||||
#include "log.h"
|
||||
#include "metrics_collections.h"
|
||||
#include "odk_structs.h"
|
||||
@@ -36,8 +36,6 @@ namespace wvcdm {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int kHttpOk = 200;
|
||||
const std::string kCencMimeType = "cenc";
|
||||
// How many seconds to fudge the start or stop playback times. We fudge these
|
||||
// times because there might be a little round off when sleeping, and because
|
||||
// the time that the license was signed or loaded might be off a little bit due
|
||||
@@ -86,27 +84,6 @@ const RenewalPolicy kHeartbeatRenewal = {"CDM_Heartbeat_renewal", 10, 30};
|
||||
|
||||
// Key ID in all duration tests.
|
||||
const KeyId kKeyId = "Duration_Key====";
|
||||
|
||||
class SimpleEventListener : public wvcdm::WvCdmEventListener {
|
||||
public:
|
||||
SimpleEventListener() { renewal_needed_ = false; }
|
||||
// We will want to know when a renewal is needed.
|
||||
void OnSessionRenewalNeeded(const CdmSessionId& /*session*/) override {
|
||||
renewal_needed_ = true;
|
||||
}
|
||||
void OnSessionKeysChange(const CdmSessionId&, const CdmKeyStatusMap&,
|
||||
bool) override {}
|
||||
void OnExpirationUpdate(const CdmSessionId&,
|
||||
int64_t expiry_time UNUSED) override {}
|
||||
bool renewal_needed() { return renewal_needed_; }
|
||||
void set_renewal_needed(bool renewal_needed) {
|
||||
renewal_needed_ = renewal_needed;
|
||||
}
|
||||
|
||||
private:
|
||||
bool renewal_needed_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
// All duration tests are parameterized by can_persist = true or false.
|
||||
@@ -114,7 +91,7 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine,
|
||||
public ::testing::WithParamInterface<bool> {
|
||||
public:
|
||||
CdmDurationTest(const std::string& content_id)
|
||||
: content_id_(content_id),
|
||||
: license_holder_(content_id, &cdm_engine_, config_),
|
||||
first_load_occurred_(false),
|
||||
allow_lenience_(false) {
|
||||
// These are reasonable initial values for most tests. This is an unlimited
|
||||
@@ -150,24 +127,22 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine,
|
||||
void SetUp() override {
|
||||
WvCdmTestBase::SetUp();
|
||||
EnsureProvisioned();
|
||||
ASSERT_NO_FATAL_FAILURE(OpenSession(&session_id_));
|
||||
can_persist_ = GetParam();
|
||||
if (can_persist_) {
|
||||
license_holder_.set_can_persist(GetParam());
|
||||
ASSERT_NO_FATAL_FAILURE(license_holder_.OpenSession());
|
||||
if (license_holder_.can_persist()) {
|
||||
// Each content/policy in a test class below should match two policies in
|
||||
// UAT. One policy id matches the string exactly, and one has
|
||||
// _can_persist appended.
|
||||
content_id_ = content_id_ + "_can_persist";
|
||||
license_type_ = kLicenseTypeOffline;
|
||||
} else {
|
||||
license_type_ = kLicenseTypeStreaming;
|
||||
license_holder_.set_content_id(license_holder_.content_id() +
|
||||
"_can_persist");
|
||||
}
|
||||
// All times in the license are relative to the rental clock.
|
||||
start_of_rental_clock_ = wvutil::Clock().GetCurrentTime();
|
||||
FetchLicense();
|
||||
license_holder_.FetchLicense();
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
cdm_engine_.CloseSession(session_id_);
|
||||
license_holder_.CloseSession();
|
||||
// Log the time used in this test suite. When this comment was written,
|
||||
// these tests took over three hours. If we want to improve that, we need to
|
||||
// track these times.
|
||||
@@ -223,99 +198,20 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine,
|
||||
return now - start_of_rental_clock_;
|
||||
}
|
||||
|
||||
void OpenSession(CdmSessionId* session_id) {
|
||||
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 CloseSession(const CdmSessionId& session_id) {
|
||||
CdmResponseType status = cdm_engine_.CloseSession(session_id);
|
||||
ASSERT_EQ(NO_ERROR, status);
|
||||
ASSERT_FALSE(cdm_engine_.IsOpenSession(session_id));
|
||||
}
|
||||
|
||||
void 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));
|
||||
ASSERT_NO_FATAL_FAILURE(GetKeyResponse(key_request));
|
||||
}
|
||||
|
||||
void GenerateKeyRequest(const InitializationData& init_data,
|
||||
CdmKeyRequest* key_request) {
|
||||
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);
|
||||
}
|
||||
|
||||
// Append the content/policy id for the test to the URL.
|
||||
std::string MakeUrl(const std::string& server_url,
|
||||
const std::string& policy_id) {
|
||||
// For these 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();
|
||||
std::string video_query;
|
||||
if (path.find("proxy.uat") != std::string::npos) {
|
||||
// This is uat or uat-nightly. Set the video_id.
|
||||
video_query = "video_id=" + policy_id;
|
||||
} else {
|
||||
// This is probably a local license server. Set the policy.
|
||||
video_query = "policy=" + 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;
|
||||
}
|
||||
|
||||
void GetKeyResponse(const CdmKeyRequest& key_request) {
|
||||
// The content id matches the policy id used on UAT.
|
||||
const std::string url = MakeUrl(config_.license_server(), content_id_);
|
||||
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) << "Error with url = " << url;
|
||||
|
||||
LicenseRequest license_request;
|
||||
license_request.GetDrmMessage(http_response, key_response_);
|
||||
}
|
||||
|
||||
// This ensures that the licenses are loaded into the sessions.
|
||||
void LoadLicense() {
|
||||
if (!first_load_occurred_) {
|
||||
CdmLicenseType license_type;
|
||||
ASSERT_EQ(KEY_ADDED, cdm_engine_.AddKey(session_id_, key_response_,
|
||||
&license_type, &key_set_id_));
|
||||
ASSERT_EQ(license_type_, license_type);
|
||||
license_holder_.LoadLicense();
|
||||
first_load_occurred_ = true;
|
||||
} else if (can_persist_) {
|
||||
// For the persistent license, we use restore key.
|
||||
ASSERT_NO_FATAL_FAILURE(OpenSession(&session_id_));
|
||||
CdmResponseType status = cdm_engine_.RestoreKey(session_id_, key_set_id_);
|
||||
ASSERT_EQ(KEY_ADDED, status);
|
||||
} else if (license_holder_.can_persist()) {
|
||||
ASSERT_NO_FATAL_FAILURE(license_holder_.OpenSession());
|
||||
license_holder_.ReloadLicense();
|
||||
}
|
||||
}
|
||||
|
||||
void UnloadLicense() {
|
||||
if (can_persist_) {
|
||||
CloseSession(session_id_);
|
||||
if (license_holder_.can_persist()) {
|
||||
license_holder_.CloseSession();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,12 +276,8 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine,
|
||||
void AllowLenience() { allow_lenience_ = true; }
|
||||
|
||||
void Decrypt() {
|
||||
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);
|
||||
const uint64_t now = CurrentRentalTime();
|
||||
EXPECT_EQ(NO_ERROR, Decrypt(session_id_, kKeyId, input, iv, &output))
|
||||
EXPECT_EQ(NO_ERROR, license_holder_.Decrypt(kKeyId))
|
||||
<< "Failed to decrypt when rental clock = " << now
|
||||
<< ", and playback clock = "
|
||||
<< ((now < start_of_playback_) ? 0 : (now - start_of_playback_));
|
||||
@@ -403,17 +295,12 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine,
|
||||
const bool low_end_device =
|
||||
(wvoec::global_features.api_version < wvoec::kCoreMessagesAPI ||
|
||||
!wvoec::global_features.usage_table);
|
||||
if (allow_lenience_ && low_end_device && can_persist_) {
|
||||
if (allow_lenience_ && low_end_device && license_holder_.can_persist()) {
|
||||
allow_success = true;
|
||||
}
|
||||
allow_lenience_ = false; // Only allow lenience once per test.
|
||||
|
||||
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);
|
||||
const uint64_t now = CurrentRentalTime();
|
||||
CdmResponseType status = Decrypt(session_id_, kKeyId, input, iv, &output);
|
||||
CdmResponseType status = license_holder_.Decrypt(kKeyId);
|
||||
// We always allow failure. that's what we usually expect.
|
||||
if (status == NEED_KEY) return;
|
||||
// No other error code is allowed: either NO_ERROR or NEED_KEY.
|
||||
@@ -428,26 +315,7 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine,
|
||||
<< (now < start_of_playback_ ? 0 : now - start_of_playback_);
|
||||
}
|
||||
|
||||
CdmResponseType Decrypt(const CdmSessionId& session_id, const KeyId& key_id,
|
||||
const std::vector<uint8_t>& input,
|
||||
const std::vector<uint8_t>& iv,
|
||||
std::vector<uint8_t>* output) {
|
||||
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);
|
||||
}
|
||||
|
||||
CdmSessionId session_id_;
|
||||
std::string content_id_;
|
||||
CdmLicenseType license_type_;
|
||||
CdmKeySetId key_set_id_;
|
||||
std::string key_response_;
|
||||
LicenseHolder license_holder_;
|
||||
// Time license requests generated. All test times are relative to this value.
|
||||
uint64_t start_of_rental_clock_;
|
||||
// The start of playback. This is set to the planned start at the beginning of
|
||||
@@ -457,9 +325,7 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine,
|
||||
// not have to reload the streaming license, and we should use RestoreKey
|
||||
// instead of AddKey for the offline license.
|
||||
bool first_load_occurred_;
|
||||
bool can_persist_;
|
||||
ODK_TimerLimits timer_limits_;
|
||||
SimpleEventListener event_listener_;
|
||||
// If this is set, then the next time we expect a playback to be terminated,
|
||||
// we will allow lenient failure.
|
||||
bool allow_lenience_;
|
||||
@@ -751,7 +617,8 @@ TEST_P(CdmUseCase_SevenHardTwoSoft, Case4) {
|
||||
LoadAndAllowPlayback(start_of_playback_,
|
||||
start_of_playback_ + 4 * kPlayDuration);
|
||||
UnloadLicense();
|
||||
if (!can_persist_) return; // streaming license cannot restart.
|
||||
// streaming license cannot restart.
|
||||
if (!license_holder_.can_persist()) return;
|
||||
AllowLenience();
|
||||
ForbidPlayback(EndOfPlaybackWindow() + kFudge);
|
||||
}
|
||||
@@ -947,7 +814,8 @@ TEST_P(CdmUseCase_SevenSoftTwoSoft, Case4) {
|
||||
LoadAndAllowPlayback(start_of_playback_,
|
||||
start_of_playback_ + 4 * kPlayDuration);
|
||||
UnloadLicense();
|
||||
if (!can_persist_) return; // streaming license cannot restart.
|
||||
// streaming license cannot restart.
|
||||
if (!license_holder_.can_persist()) return;
|
||||
AllowLenience();
|
||||
ForbidPlayback(EndOfPlaybackWindow() + kFudge);
|
||||
}
|
||||
@@ -989,7 +857,7 @@ TEST_P(CdmUseCase_SevenSoftTwoSoft, Case7) {
|
||||
EndOfPlaybackWindow() + kPlayDuration);
|
||||
UnloadLicense();
|
||||
// But forbid restart after playback window.
|
||||
if (!can_persist_) return; // streaming license cannot restart.
|
||||
if (!license_holder_.can_persist()) return;
|
||||
ForbidPlayback(EndOfPlaybackWindow() + 2 * kPlayDuration);
|
||||
}
|
||||
|
||||
@@ -1028,7 +896,7 @@ class RenewalTest : public CdmDurationTest {
|
||||
// Sleep until a renewal request event is generated. If one does not occur
|
||||
// before cutoff, an error happens.
|
||||
void SleepUntilRenewalNeeded(uint64_t cutoff) {
|
||||
while (!event_listener_.renewal_needed()) {
|
||||
while (!license_holder_.event_listener().renewal_needed()) {
|
||||
uint64_t now = CurrentRentalTime();
|
||||
ASSERT_LT(now, cutoff);
|
||||
SleepUntil(now + 1);
|
||||
@@ -1042,34 +910,18 @@ class RenewalTest : public CdmDurationTest {
|
||||
}
|
||||
|
||||
void RequestRenewal(const RenewalPolicy& renewal_policy) {
|
||||
event_listener_.set_renewal_needed(false);
|
||||
CdmKeyRequest request;
|
||||
const CdmResponseType result =
|
||||
cdm_engine_.GenerateRenewalRequest(session_id_, &request);
|
||||
ASSERT_EQ(KEY_MESSAGE, result);
|
||||
const std::string url =
|
||||
MakeUrl(config_.renewal_server(), renewal_policy.policy_id);
|
||||
renewal_in_flight_.reset(new UrlRequest(url));
|
||||
ASSERT_TRUE(renewal_in_flight_->is_connected());
|
||||
renewal_in_flight_->PostRequest(request.message);
|
||||
license_holder_.GenerateAndPostRenewalRequest(renewal_policy.policy_id);
|
||||
}
|
||||
|
||||
void LoadRenewal(uint64_t time_of_load, const RenewalPolicy& renewal_policy) {
|
||||
ASSERT_NE(renewal_in_flight_, nullptr);
|
||||
std::string http_response;
|
||||
// Most of the network latency will probably show up in the next few
|
||||
// commands. I think the tests have enough slop to account for reasonable
|
||||
// latency with the current value of kRoundTripTime. But we'll know I made a
|
||||
// mistake if we see errors about "Test Clock skew..." in the SleepUntil
|
||||
// call below.
|
||||
ASSERT_TRUE(renewal_in_flight_->GetResponse(&http_response));
|
||||
int status_code = renewal_in_flight_->GetStatusCode(http_response);
|
||||
ASSERT_EQ(kHttpOk, status_code);
|
||||
|
||||
license_holder_.FetchRenewal();
|
||||
SleepUntil(time_of_load);
|
||||
LicenseRequest license_request;
|
||||
license_request.GetDrmMessage(http_response, renewal_message_);
|
||||
EXPECT_EQ(KEY_ADDED, cdm_engine_.RenewKey(session_id_, renewal_message_));
|
||||
license_holder_.LoadRenewal();
|
||||
ComputeCutoff(time_of_load, renewal_policy);
|
||||
}
|
||||
|
||||
@@ -1260,7 +1112,7 @@ TEST_P(CdmUseCase_LicenseWithRenewal, Case4) {
|
||||
RenewAndContinue(start, load_time, stop, kShortRenewal);
|
||||
}
|
||||
UnloadLicense();
|
||||
if (can_persist_) {
|
||||
if (license_holder_.can_persist()) {
|
||||
// Reload license after timer expired.
|
||||
const uint64_t reload_time = current_cutoff_ + 20;
|
||||
ReloadAndAllowPlayback(
|
||||
@@ -1464,7 +1316,7 @@ TEST_P(CdmUseCase_LicenseWithRenewalPlayback, Case4) {
|
||||
RenewAndContinue(start, load_time, stop, kShortRenewal);
|
||||
}
|
||||
UnloadLicense();
|
||||
if (can_persist_) {
|
||||
if (license_holder_.can_persist()) {
|
||||
// Reload license after timer expired.
|
||||
const uint64_t reload_time = current_cutoff_ + 20;
|
||||
ReloadAndAllowPlayback(
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
#include "cdm_engine.h"
|
||||
#include "config_test_env.h"
|
||||
#include "license_request.h"
|
||||
#include "license_holder.h"
|
||||
#include "log.h"
|
||||
#include "oec_session_util.h"
|
||||
#include "oemcrypto_session_tests_helper.h"
|
||||
@@ -27,31 +27,35 @@
|
||||
#include "wv_cdm_constants.h"
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
using wvutil::a2b_hex;
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class WvGenericCryptoTest : public WvCdmTestBaseWithEngine {
|
||||
public:
|
||||
WvGenericCryptoTest() : holder_(&cdm_engine_) {}
|
||||
WvGenericCryptoTest() : holder_("CDM_GenericCrypto", &cdm_engine_, config_) {}
|
||||
|
||||
void SetUp() override {
|
||||
WvCdmTestBase::SetUp();
|
||||
EnsureProvisioned();
|
||||
holder_.OpenSession(config_.key_system());
|
||||
holder_.GenerateKeyRequest(binary_key_id(), ISO_BMFF_VIDEO_MIME_TYPE);
|
||||
holder_.CreateDefaultLicense();
|
||||
ASSERT_NO_FATAL_FAILURE(holder_.OpenSession());
|
||||
ASSERT_NO_FATAL_FAILURE(holder_.FetchLicense());
|
||||
|
||||
ency_id_ = "ency";
|
||||
StripeBuffer(&ency_key_, CONTENT_KEY_SIZE, 'e');
|
||||
AddOneKey(ency_id_, ency_key_, wvoec::kControlAllowEncrypt);
|
||||
dency_id_ = "dency";
|
||||
StripeBuffer(&dency_key_, CONTENT_KEY_SIZE, 'd');
|
||||
AddOneKey(dency_id_, dency_key_, wvoec::kControlAllowDecrypt);
|
||||
siggy_id_ = "siggy";
|
||||
StripeBuffer(&siggy_key_, MAC_KEY_SIZE, 's');
|
||||
AddOneKey(siggy_id_, siggy_key_, wvoec::kControlAllowSign);
|
||||
vou_id_ = "vou";
|
||||
StripeBuffer(&vou_key_, MAC_KEY_SIZE, 'v');
|
||||
AddOneKey(vou_id_, vou_key_, wvoec::kControlAllowVerify);
|
||||
ency_id_ = "encrypt-key-----";
|
||||
ency_key_ = a2b_hex("0102030405060708090a0b0c0d0e0f10");
|
||||
dency_id_ = "decrypt-key-----";
|
||||
dency_key_ = a2b_hex("AA02030405060708090a0b0c0d0e0f10");
|
||||
siggy_id_ = "sign-key--------";
|
||||
siggy_key_ = a2b_hex(
|
||||
"BB02030405060708090a0b0c0d0e0f100102030405060708090a0b0c0d0e0f10");
|
||||
vou_id_ = "verify-key------";
|
||||
vou_key_ = a2b_hex(
|
||||
"CC02030405060708090a0b0c0d0e0f100102030405060708090a0b0c0d0e0f10");
|
||||
both_id_ = "enc-and-dec-key-";
|
||||
both_key_ = a2b_hex("DD02030405060708090a0b0c0d0e0f10");
|
||||
sign_and_verify_id_ = "sign-and-verify-";
|
||||
sign_and_verify_key_ = a2b_hex(
|
||||
"EE02030405060708090a0b0c0d0e0f100102030405060708090a0b0c0d0e0f10");
|
||||
|
||||
StripeBuffer(&in_vector_, CONTENT_KEY_SIZE * 15, '1');
|
||||
in_buffer_ = std::string(in_vector_.begin(), in_vector_.end());
|
||||
@@ -62,26 +66,22 @@ class WvGenericCryptoTest : public WvCdmTestBaseWithEngine {
|
||||
|
||||
void TearDown() override { holder_.CloseSession(); }
|
||||
|
||||
// Create a single key, and add it to the license.
|
||||
void AddOneKey(const KeyId& key_id, const std::vector<uint8_t>& key_data,
|
||||
uint32_t key_control_block) {
|
||||
wvoec::KeyControlBlock block = {};
|
||||
block.control_bits = htonl(key_control_block);
|
||||
holder_.AddKey(key_id, key_data, block);
|
||||
}
|
||||
|
||||
protected:
|
||||
TestLicenseHolder holder_;
|
||||
LicenseHolder holder_;
|
||||
|
||||
KeyId ency_id_;
|
||||
KeyId dency_id_;
|
||||
KeyId siggy_id_;
|
||||
KeyId vou_id_;
|
||||
KeyId both_id_;
|
||||
KeyId sign_and_verify_id_;
|
||||
|
||||
std::vector<uint8_t> ency_key_;
|
||||
std::vector<uint8_t> dency_key_;
|
||||
std::vector<uint8_t> siggy_key_;
|
||||
std::vector<uint8_t> vou_key_;
|
||||
std::vector<uint8_t> both_key_;
|
||||
std::vector<uint8_t> sign_and_verify_key_;
|
||||
|
||||
std::vector<uint8_t> in_vector_;
|
||||
std::vector<uint8_t> iv_vector_;
|
||||
@@ -89,210 +89,155 @@ class WvGenericCryptoTest : public WvCdmTestBaseWithEngine {
|
||||
std::string iv_;
|
||||
};
|
||||
|
||||
TEST_F(WvGenericCryptoTest, LoadSpecialKeys) { holder_.SignAndLoadLicense(); }
|
||||
TEST_F(WvGenericCryptoTest, LoadSpecialKeys) {
|
||||
ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense());
|
||||
}
|
||||
|
||||
TEST_F(WvGenericCryptoTest, GenericEncryptGood) {
|
||||
CdmResponseType cdm_sts;
|
||||
ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense());
|
||||
std::string encrypted = Aes128CbcEncrypt(ency_key_, in_vector_, iv_vector_);
|
||||
std::string out_buffer;
|
||||
|
||||
holder_.SignAndLoadLicense();
|
||||
cdm_sts = cdm_engine_.GenericEncrypt(
|
||||
holder_.session_id(), in_buffer_, ency_id_, iv_,
|
||||
wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer);
|
||||
|
||||
EXPECT_EQ(NO_ERROR, cdm_sts);
|
||||
EXPECT_EQ(NO_ERROR, cdm_engine_.GenericEncrypt(
|
||||
holder_.session_id(), in_buffer_, ency_id_, iv_,
|
||||
wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer));
|
||||
EXPECT_EQ(encrypted, out_buffer);
|
||||
}
|
||||
|
||||
TEST_F(WvGenericCryptoTest, GenericEncryptNoKey) {
|
||||
CdmResponseType cdm_sts;
|
||||
ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense());
|
||||
std::string encrypted = Aes128CbcEncrypt(ency_key_, in_vector_, iv_vector_);
|
||||
std::string out_buffer;
|
||||
KeyId key_id("no_key");
|
||||
|
||||
holder_.SignAndLoadLicense();
|
||||
cdm_sts = cdm_engine_.GenericEncrypt(
|
||||
holder_.session_id(), in_buffer_, key_id, iv_,
|
||||
wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer);
|
||||
EXPECT_NE(NO_ERROR, cdm_sts);
|
||||
EXPECT_NE(NO_ERROR, cdm_engine_.GenericEncrypt(
|
||||
holder_.session_id(), in_buffer_, key_id, iv_,
|
||||
wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer));
|
||||
EXPECT_NE(encrypted, out_buffer);
|
||||
}
|
||||
|
||||
TEST_F(WvGenericCryptoTest, GenericEncryptKeyNotAllowed) {
|
||||
CdmResponseType cdm_sts;
|
||||
ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense());
|
||||
// Trying to use Decrypt key to encrypt, which is not allowed.
|
||||
KeyId key_id = dency_id_;
|
||||
std::string encrypted = Aes128CbcEncrypt(dency_key_, in_vector_, iv_vector_);
|
||||
std::string out_buffer;
|
||||
|
||||
holder_.SignAndLoadLicense();
|
||||
cdm_sts = cdm_engine_.GenericEncrypt(
|
||||
holder_.session_id(), in_buffer_, key_id, iv_,
|
||||
wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer);
|
||||
EXPECT_EQ(UNKNOWN_ERROR, cdm_sts);
|
||||
EXPECT_EQ(UNKNOWN_ERROR,
|
||||
cdm_engine_.GenericEncrypt(
|
||||
holder_.session_id(), in_buffer_, key_id, iv_,
|
||||
wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer));
|
||||
EXPECT_NE(encrypted, out_buffer);
|
||||
}
|
||||
|
||||
TEST_F(WvGenericCryptoTest, GenericDecryptGood) {
|
||||
CdmResponseType cdm_sts;
|
||||
ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense());
|
||||
std::string decrypted = Aes128CbcDecrypt(dency_key_, in_vector_, iv_vector_);
|
||||
std::string out_buffer;
|
||||
|
||||
holder_.SignAndLoadLicense();
|
||||
cdm_sts = cdm_engine_.GenericDecrypt(
|
||||
holder_.session_id(), in_buffer_, dency_id_, iv_,
|
||||
wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer);
|
||||
|
||||
EXPECT_EQ(NO_ERROR, cdm_sts);
|
||||
EXPECT_EQ(NO_ERROR, cdm_engine_.GenericDecrypt(
|
||||
holder_.session_id(), in_buffer_, dency_id_, iv_,
|
||||
wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer));
|
||||
EXPECT_EQ(decrypted, out_buffer);
|
||||
}
|
||||
|
||||
TEST_F(WvGenericCryptoTest, GenericDecryptNoKey) {
|
||||
CdmResponseType cdm_sts;
|
||||
ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense());
|
||||
std::string decrypted = Aes128CbcDecrypt(dency_key_, in_vector_, iv_vector_);
|
||||
std::string out_buffer;
|
||||
KeyId key_id = "no_key";
|
||||
|
||||
holder_.SignAndLoadLicense();
|
||||
cdm_sts = cdm_engine_.GenericDecrypt(
|
||||
holder_.session_id(), in_buffer_, key_id, iv_,
|
||||
wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer);
|
||||
EXPECT_NE(NO_ERROR, cdm_sts);
|
||||
EXPECT_NE(NO_ERROR, cdm_engine_.GenericDecrypt(
|
||||
holder_.session_id(), in_buffer_, key_id, iv_,
|
||||
wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer));
|
||||
EXPECT_NE(decrypted, out_buffer);
|
||||
}
|
||||
|
||||
TEST_F(WvGenericCryptoTest, GenericDecryptKeyNotAllowed) {
|
||||
CdmResponseType cdm_sts;
|
||||
ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense());
|
||||
// Trying to use Encrypt key to decrypt, which is not allowed.
|
||||
KeyId key_id = ency_id_;
|
||||
std::string decrypted = Aes128CbcDecrypt(ency_key_, in_vector_, iv_vector_);
|
||||
std::string out_buffer;
|
||||
|
||||
holder_.SignAndLoadLicense();
|
||||
cdm_sts = cdm_engine_.GenericDecrypt(
|
||||
holder_.session_id(), in_buffer_, key_id, iv_,
|
||||
wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer);
|
||||
EXPECT_EQ(UNKNOWN_ERROR, cdm_sts);
|
||||
EXPECT_EQ(UNKNOWN_ERROR,
|
||||
cdm_engine_.GenericDecrypt(
|
||||
holder_.session_id(), in_buffer_, key_id, iv_,
|
||||
wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer));
|
||||
EXPECT_NE(decrypted, out_buffer);
|
||||
}
|
||||
|
||||
TEST_F(WvGenericCryptoTest, GenericSignGood) {
|
||||
ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense());
|
||||
std::string out_buffer;
|
||||
std::string signature = SignHMAC(in_buffer_, siggy_key_);
|
||||
EXPECT_EQ(NO_ERROR, cdm_engine_.GenericSign(
|
||||
holder_.session_id(), in_buffer_, siggy_id_,
|
||||
wvcdm::kSigningAlgorithmHmacSha256, &out_buffer));
|
||||
EXPECT_EQ(signature, out_buffer);
|
||||
}
|
||||
|
||||
TEST_F(WvGenericCryptoTest, GenericSignKeyNotAllowed) {
|
||||
CdmResponseType cdm_sts;
|
||||
ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense());
|
||||
// Wrong key
|
||||
std::string key_id = vou_id_;
|
||||
std::string out_buffer;
|
||||
std::string signature = SignHMAC(in_buffer_, siggy_key_);
|
||||
|
||||
holder_.SignAndLoadLicense();
|
||||
cdm_sts =
|
||||
EXPECT_EQ(
|
||||
UNKNOWN_ERROR,
|
||||
cdm_engine_.GenericSign(holder_.session_id(), in_buffer_, key_id,
|
||||
wvcdm::kSigningAlgorithmHmacSha256, &out_buffer);
|
||||
EXPECT_EQ(UNKNOWN_ERROR, cdm_sts);
|
||||
wvcdm::kSigningAlgorithmHmacSha256, &out_buffer));
|
||||
EXPECT_NE(signature, out_buffer);
|
||||
}
|
||||
|
||||
TEST_F(WvGenericCryptoTest, GenericSignGood) {
|
||||
CdmResponseType cdm_sts;
|
||||
std::string out_buffer;
|
||||
std::string signature = SignHMAC(in_buffer_, siggy_key_);
|
||||
|
||||
holder_.SignAndLoadLicense();
|
||||
cdm_sts =
|
||||
cdm_engine_.GenericSign(holder_.session_id(), in_buffer_, siggy_id_,
|
||||
wvcdm::kSigningAlgorithmHmacSha256, &out_buffer);
|
||||
EXPECT_EQ(NO_ERROR, cdm_sts);
|
||||
EXPECT_EQ(signature, out_buffer);
|
||||
TEST_F(WvGenericCryptoTest, GenericVerifyGood) {
|
||||
ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense());
|
||||
std::string signature = SignHMAC(in_buffer_, vou_key_);
|
||||
EXPECT_EQ(NO_ERROR, cdm_engine_.GenericVerify(
|
||||
holder_.session_id(), in_buffer_, vou_id_,
|
||||
wvcdm::kSigningAlgorithmHmacSha256, signature));
|
||||
}
|
||||
|
||||
TEST_F(WvGenericCryptoTest, GenericVerifyKeyNotAllowed) {
|
||||
CdmResponseType cdm_sts;
|
||||
ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense());
|
||||
// Wrong key
|
||||
std::string key_id = siggy_id_;
|
||||
std::string signature = SignHMAC(in_buffer_, siggy_key_);
|
||||
|
||||
holder_.SignAndLoadLicense();
|
||||
cdm_sts =
|
||||
cdm_engine_.GenericVerify(holder_.session_id(), in_buffer_, key_id,
|
||||
wvcdm::kSigningAlgorithmHmacSha256, signature);
|
||||
EXPECT_EQ(UNKNOWN_ERROR, cdm_sts);
|
||||
EXPECT_EQ(UNKNOWN_ERROR, cdm_engine_.GenericVerify(
|
||||
holder_.session_id(), in_buffer_, key_id,
|
||||
wvcdm::kSigningAlgorithmHmacSha256, signature));
|
||||
}
|
||||
|
||||
TEST_F(WvGenericCryptoTest, GenericVerifyBadSignautre) {
|
||||
CdmResponseType cdm_sts;
|
||||
TEST_F(WvGenericCryptoTest, GenericVerifyBadSignature) {
|
||||
ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense());
|
||||
std::string signature(MAC_KEY_SIZE, 's');
|
||||
|
||||
holder_.SignAndLoadLicense();
|
||||
cdm_sts =
|
||||
cdm_engine_.GenericVerify(holder_.session_id(), in_buffer_, vou_id_,
|
||||
wvcdm::kSigningAlgorithmHmacSha256, signature);
|
||||
// OEMCrypto error is OEMCrypto_ERROR_SIGNATURE_FAILURE
|
||||
EXPECT_EQ(UNKNOWN_ERROR, cdm_sts);
|
||||
}
|
||||
|
||||
TEST_F(WvGenericCryptoTest, GenericVerifyGood) {
|
||||
CdmResponseType cdm_sts;
|
||||
std::string signature = SignHMAC(in_buffer_, vou_key_);
|
||||
|
||||
holder_.SignAndLoadLicense();
|
||||
cdm_sts =
|
||||
cdm_engine_.GenericVerify(holder_.session_id(), in_buffer_, vou_id_,
|
||||
wvcdm::kSigningAlgorithmHmacSha256, signature);
|
||||
EXPECT_EQ(NO_ERROR, cdm_sts);
|
||||
EXPECT_EQ(UNKNOWN_ERROR, cdm_engine_.GenericVerify(
|
||||
holder_.session_id(), in_buffer_, vou_id_,
|
||||
wvcdm::kSigningAlgorithmHmacSha256, signature));
|
||||
}
|
||||
|
||||
TEST_F(WvGenericCryptoTest, GenericEncryptDecrypt) {
|
||||
CdmResponseType cdm_sts;
|
||||
ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense());
|
||||
std::string encrypted = Aes128CbcEncrypt(both_key_, in_vector_, iv_vector_);
|
||||
std::string out_buffer;
|
||||
std::string clear_buffer;
|
||||
EXPECT_EQ(NO_ERROR, cdm_engine_.GenericEncrypt(
|
||||
holder_.session_id(), in_buffer_, both_id_, iv_,
|
||||
wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer));
|
||||
EXPECT_EQ(encrypted, out_buffer);
|
||||
|
||||
KeyId key_id = "enc and dec";
|
||||
|
||||
std::vector<uint8_t> key_data;
|
||||
StripeBuffer(&key_data, CONTENT_KEY_SIZE, '3');
|
||||
AddOneKey(key_id, key_data,
|
||||
wvoec::kControlAllowEncrypt | wvoec::kControlAllowDecrypt);
|
||||
|
||||
holder_.SignAndLoadLicense();
|
||||
|
||||
cdm_sts = cdm_engine_.GenericEncrypt(
|
||||
holder_.session_id(), in_buffer_, key_id, iv_,
|
||||
wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer);
|
||||
EXPECT_EQ(NO_ERROR, cdm_sts);
|
||||
|
||||
cdm_sts = cdm_engine_.GenericDecrypt(
|
||||
holder_.session_id(), out_buffer, key_id, iv_,
|
||||
wvcdm::kEncryptionAlgorithmAesCbc128, &clear_buffer);
|
||||
EXPECT_EQ(NO_ERROR, cdm_sts);
|
||||
|
||||
EXPECT_EQ(in_buffer_, clear_buffer);
|
||||
std::string decrypted = Aes128CbcDecrypt(dency_key_, in_vector_, iv_vector_);
|
||||
EXPECT_EQ(NO_ERROR, cdm_engine_.GenericDecrypt(
|
||||
holder_.session_id(), encrypted, both_id_, iv_,
|
||||
wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer));
|
||||
EXPECT_EQ(in_buffer_, out_buffer);
|
||||
}
|
||||
|
||||
TEST_F(WvGenericCryptoTest, GenericSignVerify) {
|
||||
CdmResponseType cdm_sts;
|
||||
std::string signature_buffer;
|
||||
ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense());
|
||||
|
||||
KeyId key_id = "sign and ver";
|
||||
|
||||
std::vector<uint8_t> key_data;
|
||||
StripeBuffer(&key_data, MAC_KEY_SIZE, '4');
|
||||
AddOneKey(key_id, key_data,
|
||||
wvoec::kControlAllowSign | wvoec::kControlAllowVerify);
|
||||
std::string signature = SignHMAC(in_buffer_, key_data);
|
||||
|
||||
holder_.SignAndLoadLicense();
|
||||
|
||||
cdm_sts = cdm_engine_.GenericSign(holder_.session_id(), in_buffer_, key_id,
|
||||
wvcdm::kSigningAlgorithmHmacSha256,
|
||||
&signature_buffer);
|
||||
EXPECT_EQ(NO_ERROR, cdm_sts);
|
||||
EXPECT_EQ(MAC_KEY_SIZE, signature_buffer.size());
|
||||
|
||||
cdm_sts = cdm_engine_.GenericVerify(holder_.session_id(), in_buffer_, key_id,
|
||||
wvcdm::kSigningAlgorithmHmacSha256,
|
||||
signature_buffer);
|
||||
EXPECT_EQ(NO_ERROR, cdm_sts);
|
||||
EXPECT_EQ(signature, signature_buffer);
|
||||
std::string out_buffer;
|
||||
std::string signature = SignHMAC(in_buffer_, sign_and_verify_key_);
|
||||
EXPECT_EQ(NO_ERROR, cdm_engine_.GenericSign(
|
||||
holder_.session_id(), in_buffer_, sign_and_verify_id_,
|
||||
wvcdm::kSigningAlgorithmHmacSha256, &out_buffer));
|
||||
EXPECT_EQ(signature, out_buffer);
|
||||
EXPECT_EQ(NO_ERROR, cdm_engine_.GenericVerify(
|
||||
holder_.session_id(), in_buffer_, sign_and_verify_id_,
|
||||
wvcdm::kSigningAlgorithmHmacSha256, signature));
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
227
libwvdrmengine/cdm/core/test/license_holder.cpp
Normal file
227
libwvdrmengine/cdm/core/test/license_holder.cpp
Normal file
@@ -0,0 +1,227 @@
|
||||
// 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 "oec_device_features.h"
|
||||
#include "test_base.h"
|
||||
|
||||
namespace wvcdm {
|
||||
namespace {
|
||||
constexpr int kHttpOk = 200;
|
||||
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();
|
||||
ASSERT_NO_FATAL_FAILURE(GetKeyResponse(key_request))
|
||||
<< "Failed for " << content_id();
|
||||
}
|
||||
|
||||
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::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::GenerateAndPostRenewalRequest(
|
||||
const std::string& 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();
|
||||
const std::string url = MakeUrl(config_.renewal_server(), policy_id);
|
||||
renewal_in_flight_.reset(new UrlRequest(url));
|
||||
ASSERT_TRUE(renewal_in_flight_->is_connected())
|
||||
<< "Failed for " << content_id();
|
||||
renewal_in_flight_->PostRequest(request.message);
|
||||
}
|
||||
void LicenseHolder::FetchRenewal() {
|
||||
ASSERT_NE(renewal_in_flight_, nullptr) << "Failed for " << content_id();
|
||||
ASSERT_TRUE(renewal_in_flight_->GetResponse(&renewal_response_))
|
||||
<< "Failed for " << content_id();
|
||||
int status_code = renewal_in_flight_->GetStatusCode(renewal_response_);
|
||||
ASSERT_EQ(kHttpOk, status_code) << "Failed for " << content_id();
|
||||
}
|
||||
|
||||
void LicenseHolder::LoadRenewal() {
|
||||
LicenseRequest license_request;
|
||||
license_request.GetDrmMessage(renewal_response_, renewal_message_);
|
||||
EXPECT_EQ(KEY_ADDED, cdm_engine_->RenewKey(session_id_, renewal_message_))
|
||||
<< "Failed for " << content_id();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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,
|
||||
CdmResponseType 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& 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();
|
||||
std::string video_query;
|
||||
if (!policy_id.empty()) {
|
||||
if (path.find("proxy.uat") != std::string::npos) {
|
||||
// This is uat or uat-nightly. Set the video_id.
|
||||
video_query = "video_id=" + policy_id;
|
||||
} else {
|
||||
// This is probably a local license server. Set the policy.
|
||||
video_query = "policy=" + 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_TRUE(url_request.GetResponse(&http_response));
|
||||
int status_code = url_request.GetStatusCode(http_response);
|
||||
ASSERT_EQ(kHttpOk, status_code) << "Error with url = " << url << "\n"
|
||||
<< "content_id = " << content_id() << "\n"
|
||||
<< "response = " << http_response;
|
||||
|
||||
LicenseRequest license_request;
|
||||
license_request.GetDrmMessage(http_response, key_response_);
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
133
libwvdrmengine/cdm/core/test/license_holder.h
Normal file
133
libwvdrmengine/cdm/core/test/license_holder.h
Normal file
@@ -0,0 +1,133 @@
|
||||
// Copyright 2022 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
|
||||
#ifndef WVCDM_CORE_TEST_LICENSE_HOLDER_H_
|
||||
#define WVCDM_CORE_TEST_LICENSE_HOLDER_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "cdm_engine.h"
|
||||
#include "config_test_env.h"
|
||||
#include "url_request.h"
|
||||
#include "wv_attributes.h"
|
||||
#include "wv_cdm_event_listener.h"
|
||||
|
||||
#include "log.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// An event listener to tell when a renewal is needed.
|
||||
class SimpleEventListener : public wvcdm::WvCdmEventListener {
|
||||
public:
|
||||
SimpleEventListener() { renewal_needed_ = false; }
|
||||
// We will want to know when a renewal is needed.
|
||||
void OnSessionRenewalNeeded(const CdmSessionId& /*session*/) override {
|
||||
renewal_needed_ = true;
|
||||
}
|
||||
void OnSessionKeysChange(const CdmSessionId&, const CdmKeyStatusMap&,
|
||||
bool) override {}
|
||||
void OnExpirationUpdate(const CdmSessionId&,
|
||||
int64_t expiry_time UNUSED) override {}
|
||||
bool renewal_needed() { return renewal_needed_; }
|
||||
void set_renewal_needed(bool renewal_needed) {
|
||||
renewal_needed_ = renewal_needed;
|
||||
}
|
||||
|
||||
private:
|
||||
bool renewal_needed_ = false;
|
||||
};
|
||||
|
||||
class LicenseHolder {
|
||||
public:
|
||||
// Set up a new license holder with the specified content id.
|
||||
LicenseHolder(const std::string& content_id, CdmEngine* cdm_engine,
|
||||
const ConfigTestEnv& config)
|
||||
: content_id_(content_id), cdm_engine_(cdm_engine), config_(config) {}
|
||||
~LicenseHolder();
|
||||
// Open a session for the license. Must be called before FetchLicense or
|
||||
// ReloadLicense.
|
||||
void OpenSession();
|
||||
// Close the session and release resources.
|
||||
void CloseSession();
|
||||
// Generate a license request, send it to the license server, and wait for the
|
||||
// response. The response is *not* loaded into the cdm engine.
|
||||
void FetchLicense();
|
||||
// Load the license response into the CDM engine. A call to FetchLicense()
|
||||
// must be made first.
|
||||
void LoadLicense();
|
||||
// Reload the license. Call OpenSession() before calling
|
||||
// ReloadLicense(). Also, the key_set_id must have been set previously. The
|
||||
// key_set_id is set by calling LoadLicense(), or by calling set_key_set_id().
|
||||
void ReloadLicense();
|
||||
// Generate the renewal request, and send it to the server.
|
||||
void GenerateAndPostRenewalRequest(const std::string& policy_id);
|
||||
// Fetch the renewal response. This can add a few seconds of latency.
|
||||
void FetchRenewal();
|
||||
// Load the renewal response that was fetched in FetchRenewal().
|
||||
void LoadRenewal();
|
||||
// Releases the license and frees up entry in usage table.
|
||||
void RemoveLicense();
|
||||
|
||||
// Try to decrypt some random data. This does not verify that the data is
|
||||
// decrypted correctly. Returns the result of the decrypt operation.
|
||||
CdmResponseType Decrypt(const std::string& key_id);
|
||||
// Try to decrypt some random data to a secure buffer. If the test harness
|
||||
// does not allow creating a secure buffer, then this function fails
|
||||
// immediately. Otherwise, a secure buffer is created and used for a
|
||||
// decryption operation.
|
||||
void DecryptSecure(const KeyId& key_id);
|
||||
// Try to decrypt some random data, but expect failure. The failure may
|
||||
// be either the expected_status, or NEED_KEY. We allow NEED_KEY in case
|
||||
// the server recognized that we cannot support the given key.
|
||||
void FailDecrypt(const KeyId& key_id, CdmResponseType expected_status);
|
||||
|
||||
const std::string& content_id() const { return content_id_; }
|
||||
void set_content_id(const std::string& content_id) {
|
||||
content_id_ = content_id;
|
||||
}
|
||||
// The session id. This is only valid after a call to OpenSession.
|
||||
const std::string& session_id() { return session_id_; }
|
||||
// Returns true if the license is offline.
|
||||
bool can_persist() const { return can_persist_; }
|
||||
// Sets whether the license is offline or not.
|
||||
void set_can_persist(bool can_persist) { can_persist_ = can_persist; }
|
||||
uint64_t start_of_rental_clock() const { return start_of_rental_clock_; }
|
||||
const std::string& key_set_id() const { return key_set_id_; }
|
||||
void set_key_set_id(const std::string& key_set_id) {
|
||||
key_set_id_ = key_set_id;
|
||||
}
|
||||
SimpleEventListener& event_listener() { return event_listener_; }
|
||||
|
||||
private:
|
||||
std::string content_id_;
|
||||
CdmSessionId session_id_;
|
||||
CdmKeySetId key_set_id_;
|
||||
std::string key_response_;
|
||||
bool can_persist_ = false;
|
||||
uint64_t start_of_rental_clock_ = 0u;
|
||||
CdmEngine* cdm_engine_ = nullptr;
|
||||
const ConfigTestEnv& config_;
|
||||
SimpleEventListener event_listener_;
|
||||
std::unique_ptr<UrlRequest> renewal_in_flight_;
|
||||
std::string renewal_message_;
|
||||
std::string renewal_response_;
|
||||
|
||||
// Generate the license request.
|
||||
void GenerateKeyRequest(const InitializationData& init_data,
|
||||
CdmKeyRequest* key_request);
|
||||
|
||||
// Generate a URL for the specified policy. The license request should be sent
|
||||
// to this url.
|
||||
std::string MakeUrl(const std::string& server_url,
|
||||
const std::string& policy_id);
|
||||
// Fetch the key response from the server.
|
||||
void GetKeyResponse(const CdmKeyRequest& key_request);
|
||||
};
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
#endif // WVCDM_CORE_TEST_LICENSE_HOLDER_H_
|
||||
@@ -13,27 +13,15 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "cdm_engine.h"
|
||||
#include "clock.h"
|
||||
#include "config_test_env.h"
|
||||
#include "initialization_data.h"
|
||||
#include "license_request.h"
|
||||
#include "license_holder.h"
|
||||
#include "log.h"
|
||||
#include "metrics_collections.h"
|
||||
#include "oec_device_features.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:
|
||||
@@ -41,210 +29,44 @@ class CorePIGTest : public WvCdmTestBaseWithEngine {
|
||||
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<uint8_t> input(buffer_size, 0);
|
||||
std::vector<uint8_t> output(buffer_size, 0);
|
||||
const std::vector<uint8_t> iv(KEY_IV_SIZE, 0);
|
||||
ASSERT_EQ(NO_ERROR, Decrypt(session_id, key_id, input, iv, &output));
|
||||
}
|
||||
|
||||
// Try to use the key to decrypt, but expect the key has expired.
|
||||
void FailDecrypt(const CdmSessionId& session_id, const KeyId& key_id,
|
||||
CdmResponseType expected_status) {
|
||||
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);
|
||||
CdmResponseType status = Decrypt(session_id, key_id, input, iv, &output);
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
// Decrypt or fail to decrypt, with the expected status.
|
||||
CdmResponseType Decrypt(const CdmSessionId& session_id, const KeyId& key_id,
|
||||
const std::vector<uint8_t>& input,
|
||||
const std::vector<uint8_t>& iv,
|
||||
std::vector<uint8_t>* output) {
|
||||
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);
|
||||
}
|
||||
|
||||
// Use the key to decrypt to a secure buffer.
|
||||
void DecryptSecure(const CdmSessionId& session_id, 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.
|
||||
OEMCrypto_FreeSecureBuffer(oec_session_id, &output_descriptor,
|
||||
secure_buffer_fid);
|
||||
|
||||
ASSERT_EQ(status, NO_ERROR);
|
||||
}
|
||||
};
|
||||
|
||||
// An offline license with nonce not required.
|
||||
TEST_F(CorePIGTest, OfflineNoNonce) {
|
||||
const std::string content_id = "2015_tears";
|
||||
LicenseHolder holder("CDM_OfflineNoNonce", &cdm_engine_, config_);
|
||||
holder.set_can_persist(true);
|
||||
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));
|
||||
ASSERT_NO_FATAL_FAILURE(holder.OpenSession());
|
||||
ASSERT_NO_FATAL_FAILURE(holder.FetchLicense());
|
||||
ASSERT_NO_FATAL_FAILURE(holder.LoadLicense());
|
||||
EXPECT_EQ(NO_ERROR, holder.Decrypt(key_id));
|
||||
ASSERT_NO_FATAL_FAILURE(holder.CloseSession());
|
||||
// Should be able to close the previous session, open a new session,
|
||||
// and reload the license.
|
||||
ASSERT_NO_FATAL_FAILURE(holder.OpenSession());
|
||||
ASSERT_NO_FATAL_FAILURE(holder.ReloadLicense());
|
||||
EXPECT_EQ(NO_ERROR, holder.Decrypt(key_id));
|
||||
ASSERT_NO_FATAL_FAILURE(holder.CloseSession());
|
||||
}
|
||||
|
||||
// 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));
|
||||
LicenseHolder holder("CDM_OfflineWithPST", &cdm_engine_, config_);
|
||||
holder.set_can_persist(true);
|
||||
const KeyId key_id = "0000000000000000";
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(holder.OpenSession());
|
||||
ASSERT_NO_FATAL_FAILURE(holder.FetchLicense());
|
||||
ASSERT_NO_FATAL_FAILURE(holder.LoadLicense());
|
||||
EXPECT_EQ(NO_ERROR, holder.Decrypt(key_id));
|
||||
ASSERT_NO_FATAL_FAILURE(holder.CloseSession());
|
||||
// Should be able to close the previous session, open a new session,
|
||||
// and reload the license.
|
||||
ASSERT_NO_FATAL_FAILURE(holder.OpenSession());
|
||||
ASSERT_NO_FATAL_FAILURE(holder.ReloadLicense());
|
||||
EXPECT_EQ(NO_ERROR, holder.Decrypt(key_id));
|
||||
ASSERT_NO_FATAL_FAILURE(holder.CloseSession());
|
||||
}
|
||||
|
||||
// This test verifies that the system can download and install license with a
|
||||
@@ -252,31 +74,39 @@ TEST_F(CorePIGTest, OfflineWithPST) {
|
||||
// a non-secure buffer using this key, but that we can decrypt to a secure
|
||||
// buffer, if the test harness supports secure buffers.
|
||||
TEST_F(CorePIGTest, OfflineHWSecureRequired) {
|
||||
const std::string content_id = "GTS_HW_SECURE_ALL";
|
||||
const KeyId key_id = "0000000000000002";
|
||||
LicenseHolder holder("CDM_OfflineHWSecureRequired", &cdm_engine_, config_);
|
||||
holder.set_can_persist(true);
|
||||
const KeyId sw_key_id = "0000000000000000";
|
||||
const KeyId hw_key_id = "0000000000000001";
|
||||
|
||||
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));
|
||||
// First we try to decrypt to a non-secure buffer and verify that it fails.
|
||||
// TODO(b/164517875): This error code should be something actionable.
|
||||
ASSERT_NO_FATAL_FAILURE(FailDecrypt(session_id, key_id, DECRYPT_ERROR));
|
||||
ASSERT_NO_FATAL_FAILURE(holder.OpenSession());
|
||||
ASSERT_NO_FATAL_FAILURE(holder.FetchLicense());
|
||||
ASSERT_NO_FATAL_FAILURE(holder.LoadLicense());
|
||||
EXPECT_EQ(NO_ERROR, holder.Decrypt(sw_key_id));
|
||||
ASSERT_NO_FATAL_FAILURE(holder.FailDecrypt(hw_key_id, DECRYPT_ERROR));
|
||||
// Next, if possible, we try to decrypt to a secure buffer, and verify
|
||||
// success.
|
||||
if (wvoec::global_features.test_secure_buffers) {
|
||||
ASSERT_NO_FATAL_FAILURE(DecryptSecure(session_id, key_id));
|
||||
ASSERT_NO_FATAL_FAILURE(holder.DecryptSecure(hw_key_id));
|
||||
} else {
|
||||
LOGI("Test harness cannot create secure buffers. test skipped.");
|
||||
}
|
||||
ASSERT_NO_FATAL_FAILURE(CloseSession(session_id));
|
||||
ASSERT_NO_FATAL_FAILURE(holder.CloseSession());
|
||||
|
||||
// Should be able to close the previous session, open a new session,
|
||||
// and reload the license.
|
||||
ASSERT_NO_FATAL_FAILURE(holder.OpenSession());
|
||||
ASSERT_NO_FATAL_FAILURE(holder.ReloadLicense());
|
||||
EXPECT_EQ(NO_ERROR, holder.Decrypt(sw_key_id));
|
||||
ASSERT_NO_FATAL_FAILURE(holder.FailDecrypt(hw_key_id, DECRYPT_ERROR));
|
||||
// Next, if possible, we try to decrypt to a secure buffer, and verify
|
||||
// success.
|
||||
if (wvoec::global_features.test_secure_buffers) {
|
||||
ASSERT_NO_FATAL_FAILURE(holder.DecryptSecure(hw_key_id));
|
||||
} else {
|
||||
LOGI("Test harness cannot create secure buffers. test skipped.");
|
||||
}
|
||||
ASSERT_NO_FATAL_FAILURE(holder.CloseSession());
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
@@ -10,16 +10,23 @@
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include "license_holder.h"
|
||||
#include "log.h"
|
||||
#include "test_sleep.h"
|
||||
|
||||
using wvutil::a2b_hex;
|
||||
using wvutil::b2a_hex;
|
||||
using wvutil::FileSystem;
|
||||
using wvutil::TestSleep;
|
||||
using wvutil::unlimited_b2a_hex;
|
||||
|
||||
namespace wvcdm {
|
||||
FileSystem* RebootTest::file_system_;
|
||||
|
||||
namespace {
|
||||
// How much fudge or round off error do we allow in license durations for reboot
|
||||
// tests.
|
||||
constexpr int64_t kFudge = 10;
|
||||
|
||||
// We will encode a value string by wrapping it in braces, or as hex.
|
||||
// If the string is not printable, or if it has unmatched braces, then we use
|
||||
// hex. Otherwise, we surround the whole string with braces.
|
||||
@@ -33,11 +40,11 @@ std::string EncodeString(const std::string& data) {
|
||||
// If there are any unprintable characters, except whitespace, or if we
|
||||
// close a brace before we open it, then just use hex.
|
||||
if (!printable || braces_count < 0) {
|
||||
return "0x" + b2a_hex(data) + ",";
|
||||
return "0x" + unlimited_b2a_hex(data) + ",";
|
||||
}
|
||||
}
|
||||
// If we left any braces open, then use hex.
|
||||
if (braces_count != 0) return "0x" + b2a_hex(data) + ",";
|
||||
if (braces_count != 0) return "0x" + unlimited_b2a_hex(data) + ",";
|
||||
return "{" + data + "},";
|
||||
}
|
||||
|
||||
@@ -51,12 +58,12 @@ std::string EncodeKey(const std::string& data) {
|
||||
}
|
||||
// When decoding, we assume that a key starting with "0x" is in hex. So we
|
||||
// can't have any keys that start with "0x".
|
||||
if (data.substr(0, 2) == "0x") return "0x" + b2a_hex(data) + ":";
|
||||
if (data.substr(0, 2) == "0x") return "0x" + unlimited_b2a_hex(data) + ":";
|
||||
// If the key is just is not printable, or if it has unmatched braces, then
|
||||
// we use hex. Otherwise, we surround the whole string with braces.
|
||||
for (size_t i = 0; i < data.length(); i++) {
|
||||
if (!isprint(data[i]) || (data[i] == ':')) {
|
||||
return "0x" + b2a_hex(data) + ":";
|
||||
return "0x" + unlimited_b2a_hex(data) + ":";
|
||||
}
|
||||
}
|
||||
return data + ":";
|
||||
@@ -227,6 +234,23 @@ void RebootTest::TearDown() {
|
||||
WvCdmTestBase::TearDown();
|
||||
}
|
||||
|
||||
int64_t RebootTest::LoadTime(const std::string& key) {
|
||||
int64_t value = 0;
|
||||
std::istringstream input(persistent_data_[key]);
|
||||
input >> value;
|
||||
if (input.fail()) {
|
||||
LOGE("Could not parse time '%s'", persistent_data_[key].c_str());
|
||||
}
|
||||
if (!input.eof()) {
|
||||
LOGE("Extra text at end of time '%s'", persistent_data_[key].c_str());
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
void RebootTest::SaveTime(const std::string& key, int64_t time) {
|
||||
persistent_data_[key] = std::to_string(time);
|
||||
}
|
||||
|
||||
/** Test the dump and restore functions above. This does not test CDM
|
||||
functionality. */
|
||||
TEST_F(RebootTest, TestDumpUtil) {
|
||||
@@ -247,6 +271,10 @@ TEST_F(RebootTest, TestDumpUtil) {
|
||||
// This key looks like it might be hex. It should show up as hex in the
|
||||
// save file.
|
||||
map1["0x_bad_key_00"] = "value is ok";
|
||||
std::string big_string = "start with something {binary";
|
||||
// Double big_string 8 times, i.e. times 256, so it's bigger than 2k:
|
||||
for (int i = 0; i < 8; i++) big_string = big_string + big_string;
|
||||
map1["big_file"] = big_string;
|
||||
const std::string dump2 = DumpData(map1);
|
||||
std::map<std::string, std::string> map3;
|
||||
EXPECT_TRUE(ParseDump(dump2, &map3));
|
||||
@@ -273,4 +301,429 @@ TEST_F(RebootTest, FilesArePersistent) {
|
||||
}
|
||||
}
|
||||
|
||||
/** Verify that the clock moves forward over a reboot. */
|
||||
TEST_F(RebootTest, TimeMovesForward) {
|
||||
wvutil::TestSleep::Sleep(2);
|
||||
const int64_t start = wvutil::Clock().GetCurrentTime();
|
||||
wvutil::TestSleep::Sleep(2);
|
||||
const int64_t end = wvutil::Clock().GetCurrentTime();
|
||||
EXPECT_NEAR(end - start, 2.0, 1.0);
|
||||
const std::string key = "end_time";
|
||||
if (test_pass() == 0) {
|
||||
// Save off the end of pass 1.
|
||||
SaveTime(key, end);
|
||||
} else {
|
||||
int64_t previous_end = LoadTime(key);
|
||||
EXPECT_LT(previous_end, start);
|
||||
}
|
||||
}
|
||||
|
||||
/** Test offline license durations work correctly after reboot. For time
|
||||
constraints, this runs four sets of tests in parallel. For each set of tests
|
||||
we load a list of licenses with a range of rental or playback durations so
|
||||
that we can be confident that a short wait will find at least one duration
|
||||
in the range that has expired and at least one duration that has not
|
||||
expired.
|
||||
|
||||
RDa. Load an offline license, reboot the device, reload the license and
|
||||
verify that the rental duration is enforced from the time the license was
|
||||
requested.
|
||||
|
||||
RDb. Load an offline license, begin playback, reboot the device, reload the
|
||||
license and verify that the rental duration is enforced from the time the
|
||||
license was requested.
|
||||
|
||||
PDa. Load an offline license, reboot the device, reload the license and
|
||||
verify that the playback duration is enforced from the time initial playback
|
||||
started.
|
||||
|
||||
PDb. Load an offline license, begin playback, reboot the device, reload the
|
||||
license and verify that the playback duration is enforced from the time
|
||||
initial playback started.
|
||||
|
||||
Each of these four sets contains licenses with various durations so that the
|
||||
test can run with a reasonable reboot time. We will assume that a reboot can
|
||||
take anywhere from 10 seconds to almost an hour. With this in mind, we will
|
||||
create a license that has a 10 second duration, one with a 20 second
|
||||
duration, ... and one with an hour duration. All of these licenses will be
|
||||
loaded before the reboot. Then after the reboot we should be in a situation
|
||||
like this:
|
||||
:-----------------------------------------> time axis.
|
||||
: start of test.
|
||||
:: licenses loaded.
|
||||
:: : reboot starts
|
||||
:: : : reboot finished. test resumes.
|
||||
:: : :
|
||||
:[---] 10s - expired :
|
||||
:[-------] 20s expired :
|
||||
: ... :
|
||||
:[---------------------------] this license has not yet expired
|
||||
:[----------------------------------------------] license not expired.
|
||||
: ... :
|
||||
:[---------------------------------------------------] 1 hour. not expired
|
||||
|
||||
After the test resumes, we will have at least one license that has not yet
|
||||
expired. We will then sleep we are near the expiration time of that
|
||||
license. We can then carefully test that this one license from the set
|
||||
enforces its duration.
|
||||
|
||||
This is complicated by the fact that we are testing four different sets. So
|
||||
what we really have is something like this:
|
||||
:-----------------------------------------> time axis.
|
||||
: start of test.
|
||||
:: licenses loaded.
|
||||
:: : reboot starts
|
||||
:: : : reboot finished. test resumes.
|
||||
:: : :
|
||||
:[-----------------------------] first RDa license that has not exipred.
|
||||
:[--------------------------] first RDb license that has not exipred.
|
||||
:[---------------------------] first PDa license that has not exipred.
|
||||
:[-------------------------------] first PDb license that has not exipred.
|
||||
|
||||
Since we want to test all four of these licenses, we will compute the
|
||||
interesting times for each license. These are the times just before, and
|
||||
just after the expiration time. After sorting these interesting times, we
|
||||
will test each one in order.
|
||||
*/
|
||||
class OfflineLicense {
|
||||
public:
|
||||
OfflineLicense(const std::string& test_type, CdmEngine* cdm_engine,
|
||||
const ConfigTestEnv& config, int64_t duration,
|
||||
bool play_before_reboot)
|
||||
: content_id_("CDM_Reboot_" + test_type +
|
||||
(play_before_reboot ? "b_" : "a_") +
|
||||
std::to_string(duration)),
|
||||
duration_(duration),
|
||||
play_before_reboot_(play_before_reboot),
|
||||
license_holder_(content_id_, cdm_engine, config) {
|
||||
license_holder_.set_can_persist(true);
|
||||
}
|
||||
|
||||
virtual ~OfflineLicense() {}
|
||||
|
||||
// Fetch and load the license. The session is left open.
|
||||
void LoadLicense() {
|
||||
license_holder_.OpenSession();
|
||||
start_of_rental_clock_ = wvutil::Clock().GetCurrentTime();
|
||||
license_holder_.FetchLicense();
|
||||
license_holder_.LoadLicense();
|
||||
}
|
||||
|
||||
// Reload the license. The session is left open.
|
||||
void ReloadLicense() {
|
||||
license_holder_.OpenSession();
|
||||
license_holder_.ReloadLicense();
|
||||
}
|
||||
|
||||
// Action to be taken after loading the license, but before the reboot.
|
||||
virtual void BeforeReboot() {
|
||||
if (play_before_reboot_) {
|
||||
Decrypt();
|
||||
}
|
||||
}
|
||||
|
||||
// Action to be taken after reloading the license.
|
||||
virtual void AfterReboot() {}
|
||||
|
||||
void CloseSession() { license_holder_.CloseSession(); }
|
||||
|
||||
// The time this license should be cutoff. Decrypt should succeed before this
|
||||
// time and should fail after this time.
|
||||
virtual int64_t cutoff() = 0;
|
||||
|
||||
// Verify that the license may be used to decrypt content.
|
||||
void Decrypt() {
|
||||
if (start_of_playback_ == 0) {
|
||||
start_of_playback_ = wvutil::Clock().GetCurrentTime();
|
||||
}
|
||||
const KeyId key_id = "0000000000000000";
|
||||
EXPECT_EQ(NO_ERROR, license_holder_.Decrypt(key_id))
|
||||
<< "Failed for " << content_id_
|
||||
<< ", now = " << wvutil::Clock().GetCurrentTime() << ", rental_clock="
|
||||
<< (wvutil::Clock().GetCurrentTime() - start_of_rental_clock_)
|
||||
<< ", playback_clock = "
|
||||
<< (wvutil::Clock().GetCurrentTime() - start_of_playback_)
|
||||
<< ", delta to cutoff = "
|
||||
<< (wvutil::Clock().GetCurrentTime() - cutoff());
|
||||
}
|
||||
|
||||
// Verify that the license has expired, and may not be used to decrypt
|
||||
// content.
|
||||
void FailDecrypt() {
|
||||
const KeyId key_id = "0000000000000000";
|
||||
EXPECT_EQ(NEED_KEY, license_holder_.Decrypt(key_id))
|
||||
<< "Decrypt should have failed for " << content_id_
|
||||
<< ", now = " << wvutil::Clock().GetCurrentTime() << ", rental_clock="
|
||||
<< (wvutil::Clock().GetCurrentTime() - start_of_rental_clock_)
|
||||
<< ", playback_clock = "
|
||||
<< (wvutil::Clock().GetCurrentTime() - start_of_playback_)
|
||||
<< ", delta to cutoff = "
|
||||
<< (wvutil::Clock().GetCurrentTime() - cutoff());
|
||||
}
|
||||
|
||||
// Save times and the key set id to persistent data.
|
||||
void SaveData(RebootTest* reboot_test,
|
||||
std::map<std::string, std::string>* persistent_data) {
|
||||
reboot_test->SaveTime("start_of_rental_" + content_id_,
|
||||
start_of_rental_clock_);
|
||||
reboot_test->SaveTime("start_of_playback_" + content_id_,
|
||||
start_of_playback_);
|
||||
(*persistent_data)["key_set_id_" + content_id_] =
|
||||
license_holder_.key_set_id();
|
||||
}
|
||||
|
||||
// Load times and the key set id from persistent data.
|
||||
void LoadData(RebootTest* reboot_test,
|
||||
std::map<std::string, std::string>* persistent_data) {
|
||||
start_of_rental_clock_ =
|
||||
reboot_test->LoadTime("start_of_rental_" + content_id_);
|
||||
start_of_playback_ =
|
||||
reboot_test->LoadTime("start_of_playback_" + content_id_);
|
||||
license_holder_.set_key_set_id(
|
||||
(*persistent_data)["key_set_id_" + content_id_]);
|
||||
}
|
||||
|
||||
const std::string& content_id() const { return content_id_; }
|
||||
|
||||
protected:
|
||||
const std::string content_id_;
|
||||
int64_t duration_;
|
||||
int64_t start_of_rental_clock_ = 0;
|
||||
int64_t start_of_playback_ = 0;
|
||||
bool play_before_reboot_;
|
||||
LicenseHolder license_holder_;
|
||||
};
|
||||
|
||||
// Holds an offline license that has a limit on the rental duration.
|
||||
class RentalDurationLicense : public OfflineLicense {
|
||||
public:
|
||||
RentalDurationLicense(CdmEngine* cdm_engine, const ConfigTestEnv& config,
|
||||
int64_t duration, bool play_before_reboot)
|
||||
: OfflineLicense("RD", cdm_engine, config, duration, play_before_reboot) {
|
||||
}
|
||||
|
||||
int64_t cutoff() override { return start_of_rental_clock_ + duration_; }
|
||||
};
|
||||
|
||||
// Holds an offline license that has a limit on the playback duration.
|
||||
class PlaybackDurationLicense : public OfflineLicense {
|
||||
public:
|
||||
PlaybackDurationLicense(CdmEngine* cdm_engine, const ConfigTestEnv& config,
|
||||
int64_t duration, bool play_before_reboot)
|
||||
: OfflineLicense("PD", cdm_engine, config, duration, play_before_reboot) {
|
||||
}
|
||||
|
||||
// If we did not start playback before the reboot, we will start playback
|
||||
// just after reloading the license, post-reboot.
|
||||
void AfterReboot() override {
|
||||
if (!play_before_reboot_) {
|
||||
Decrypt();
|
||||
}
|
||||
}
|
||||
|
||||
int64_t cutoff() override { return start_of_playback_ + duration_; }
|
||||
};
|
||||
|
||||
// Test that the rental and playback durations are enforced across a reboot.
|
||||
class OfflineLicenseTest : public RebootTest {
|
||||
public:
|
||||
void SetUp() override {
|
||||
RebootTest::SetUp();
|
||||
EnsureProvisioned();
|
||||
// Run each of the following test cases in parallel so that we don't have to
|
||||
// sleep separately for each one. These durations need to match the polices
|
||||
// specified on the UAT license server.
|
||||
test_case_.resize(4);
|
||||
const std::vector<int64_t> duration_range = {10, 20, 30, 45,
|
||||
60, 300, 900, 3600};
|
||||
for (size_t i = 0; i < duration_range.size(); i++) {
|
||||
test_case_[0].push_back(
|
||||
std::unique_ptr<OfflineLicense>(new RentalDurationLicense(
|
||||
&cdm_engine_, config_, duration_range[i], false)));
|
||||
test_case_[1].push_back(
|
||||
std::unique_ptr<OfflineLicense>(new RentalDurationLicense(
|
||||
&cdm_engine_, config_, duration_range[i], true)));
|
||||
test_case_[2].push_back(
|
||||
std::unique_ptr<OfflineLicense>(new PlaybackDurationLicense(
|
||||
&cdm_engine_, config_, duration_range[i], false)));
|
||||
test_case_[3].push_back(
|
||||
std::unique_ptr<OfflineLicense>(new PlaybackDurationLicense(
|
||||
&cdm_engine_, config_, duration_range[i], true)));
|
||||
}
|
||||
}
|
||||
|
||||
// Load all of the licenses. For the tests that require playback before the
|
||||
// reboot, we start playback here.
|
||||
void LoadAllLicenses() {
|
||||
DeleteAllLicenses();
|
||||
// For each test case, load an offline license and save the data.
|
||||
for (size_t n = 0; n < test_case_.size(); n++) {
|
||||
for (size_t i = 0; i < test_case_[n].size(); i++) {
|
||||
ASSERT_NO_FATAL_FAILURE(test_case_[n][i]->LoadLicense());
|
||||
test_case_[n][i]->BeforeReboot(); // For some tests, we decrypt here.
|
||||
test_case_[n][i]->SaveData(this, &persistent_data_);
|
||||
test_case_[n][i]->CloseSession();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reload all of the licenses. We also go through each license and figure out
|
||||
// which ones have already expired, or close to expire. We then find the first
|
||||
// license from each set that will be next to expire, so that we can test
|
||||
// those more carefully.
|
||||
void ReloadAllLicense() {
|
||||
// first_valid is the index of the first license that has not expired and is
|
||||
// not just about to expire.
|
||||
first_valid_.resize(test_case_.size());
|
||||
for (size_t n = 0; n < test_case_.size(); n++) {
|
||||
for (size_t i = 0; i < test_case_[n].size(); i++) {
|
||||
OfflineLicense* license = test_case_[n][i].get();
|
||||
license->LoadData(this, &persistent_data_);
|
||||
ASSERT_NO_FATAL_FAILURE(license->ReloadLicense());
|
||||
license->AfterReboot();
|
||||
// if past cutoff. make sure decrypt fails.
|
||||
if (license->cutoff() + kFudge < wvutil::Clock().GetCurrentTime()) {
|
||||
license->FailDecrypt();
|
||||
}
|
||||
// If past cutoff, or near cutoff, then we don't want to use it
|
||||
// as our first valid.
|
||||
if (license->cutoff() - 2 * kFudge < wvutil::Clock().GetCurrentTime()) {
|
||||
first_valid_[n] = i + 1;
|
||||
}
|
||||
license->CloseSession();
|
||||
}
|
||||
// We expect there to be at least one license that has not expired yet.
|
||||
// If this is not true, then the reboot time was probably longer than
|
||||
// expected. If it is important to run these tests with a very long reboot
|
||||
// time, then please ask a Widevine engineer to generate more license
|
||||
// policies with longer reboot times.
|
||||
ASSERT_LT(first_valid_[n], test_case_[n].size())
|
||||
<< "For n=" << n
|
||||
<< ", content_id = " << test_case_[n][0]->content_id()
|
||||
<< ", time=" << wvutil::Clock().GetCurrentTime() << "\n"
|
||||
<< "This is an indication that the reboot took a very long time.\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Take the first_valid_ array, which tells us which licenses will expire
|
||||
// soon, and compute the times we are interested in: a little before and a
|
||||
// little after the cutoff. We want to test that the license is valid just
|
||||
// before the cutoff and that the license is not valid just after the
|
||||
// cutoff.
|
||||
//
|
||||
// The interesting_times_ is a std::set, so that we may iterate through the
|
||||
// times in order.
|
||||
void FindInterestingTimes() {
|
||||
for (size_t n = 0; n < test_case_.size(); n++) {
|
||||
OfflineLicense* license = test_case_[n][first_valid_[n]].get();
|
||||
interesting_times_.insert(license->cutoff() - kFudge);
|
||||
interesting_times_.insert(license->cutoff() + kFudge);
|
||||
}
|
||||
}
|
||||
|
||||
// Test at each of the interesting times. These are the times just before and
|
||||
// after the cutoff of the next license to expire from each list of test
|
||||
// cases.
|
||||
void TestInterestingTimes() {
|
||||
int decrypt_count = 0;
|
||||
int fail_count = 0;
|
||||
for (auto time : interesting_times_) {
|
||||
int64_t now = wvutil::Clock().GetCurrentTime();
|
||||
int64_t delta = (time - now);
|
||||
// It is not necessarily an error for the delta to be negative. But it is
|
||||
// an indication that the we are near an error condition. If the current
|
||||
// time is a few seconds past the interesting time, then the license
|
||||
// should still be valid or expired. If delta is very large relative to
|
||||
// kFudge, then the test might fail. This is still an indication that
|
||||
// something is wrong: reloading a license and decrypting some content
|
||||
// should not take multiple seconds.
|
||||
if (delta < 0) LOGW("Sleep delta would be negative: %ld", delta);
|
||||
if (delta > 0) TestSleep::Sleep(static_cast<unsigned int>(delta));
|
||||
// We look at each of the four license that we used to generate the
|
||||
// interesting times. It's possible that two licenses share the same
|
||||
// interesting time, so we have to check each of the four against each
|
||||
// time instead of keeping track of which license generated which time.
|
||||
for (size_t n = 0; n < test_case_.size(); n++) {
|
||||
OfflineLicense* license = test_case_[n][first_valid_[n]].get();
|
||||
ASSERT_NO_FATAL_FAILURE(license->ReloadLicense());
|
||||
if (time == license->cutoff() - kFudge) {
|
||||
license->Decrypt();
|
||||
decrypt_count++;
|
||||
}
|
||||
if (time == license->cutoff() + kFudge) {
|
||||
license->FailDecrypt();
|
||||
fail_count++;
|
||||
}
|
||||
license->CloseSession();
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(decrypt_count, 4) << "Test error. I missed a cutoff";
|
||||
EXPECT_EQ(fail_count, 4) << "Test error. I missed a cutoff";
|
||||
}
|
||||
|
||||
// After we have tested one license from each list of test cases carefully,
|
||||
// all the rest of the licenses can be tested. We do not sleep in this
|
||||
// function, we only test each license that is still valid, or has already
|
||||
// expired. We ignore licenses that are near the cutoff because we already
|
||||
// tested those license in the previous function.
|
||||
void TestAfterInterestingTimes() {
|
||||
// Make sure that all the rest of the licenses are still valid.
|
||||
for (size_t n = 0; n < test_case_.size(); n++) {
|
||||
for (size_t i = first_valid_[n] + 1; i < test_case_[n].size(); i++) {
|
||||
OfflineLicense* license = test_case_[n][i].get();
|
||||
ASSERT_NO_FATAL_FAILURE(license->ReloadLicense());
|
||||
int64_t now = wvutil::Clock().GetCurrentTime();
|
||||
if (now <= license->cutoff() - kFudge) {
|
||||
license->Decrypt();
|
||||
}
|
||||
if (now >= license->cutoff() + kFudge) {
|
||||
license->FailDecrypt();
|
||||
}
|
||||
license->CloseSession();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
// Clean up licenses if any test failed.
|
||||
if (::testing::Test::HasFailure() || test_pass() == 1) {
|
||||
DeleteAllLicenses();
|
||||
}
|
||||
RebootTest::TearDown();
|
||||
}
|
||||
|
||||
void DeleteAllLicenses() {
|
||||
std::vector<std::string> key_set_ids;
|
||||
EXPECT_EQ(NO_ERROR,
|
||||
cdm_engine_.ListStoredLicenses(kSecurityLevelL1, &key_set_ids));
|
||||
for (auto key_set : key_set_ids) {
|
||||
cdm_engine_.RemoveOfflineLicense(key_set, kSecurityLevelL1);
|
||||
}
|
||||
// TODO(b/215230202): Is this necessary? It doesn't seem to work.
|
||||
std::vector<std::string> ksids;
|
||||
std::vector<std::string> pst;
|
||||
std::string app_id = "";
|
||||
EXPECT_EQ(NO_ERROR,
|
||||
cdm_engine_.ListUsageIds(app_id, kSecurityLevelL1, &ksids, &pst));
|
||||
for (auto k : ksids) {
|
||||
EXPECT_EQ(NO_ERROR,
|
||||
cdm_engine_.DeleteUsageRecord(app_id, kSecurityLevelL1, k));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::vector<std::unique_ptr<OfflineLicense>>> test_case_;
|
||||
std::set<int64_t> interesting_times_;
|
||||
std::vector<size_t> first_valid_;
|
||||
};
|
||||
|
||||
TEST_F(OfflineLicenseTest, VariousTests) {
|
||||
if (test_pass() == 0) {
|
||||
LoadAllLicenses();
|
||||
} else if (test_pass() == 1) {
|
||||
ReloadAllLicense();
|
||||
FindInterestingTimes();
|
||||
TestInterestingTimes();
|
||||
TestAfterInterestingTimes();
|
||||
}
|
||||
}
|
||||
} // namespace wvcdm
|
||||
|
||||
@@ -36,6 +36,12 @@ class RebootTest : public WvCdmTestBaseWithEngine {
|
||||
|
||||
static int test_pass() { return default_config_.test_pass(); }
|
||||
|
||||
// Load a previously saved time. Returns 0 if the value does not exist or
|
||||
// cannot be parsed.
|
||||
int64_t LoadTime(const std::string& key);
|
||||
// Save a time to persistent storage.
|
||||
void SaveTime(const std::string& key, int64_t time);
|
||||
|
||||
protected:
|
||||
void SetUp() override;
|
||||
void TearDown() override;
|
||||
|
||||
@@ -122,6 +122,11 @@ void show_menu(const char* prog_name, const std::string& extra_help_text) {
|
||||
<< " 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
|
||||
@@ -442,7 +447,9 @@ void WvCdmTestBase::EnsureProvisioned() {
|
||||
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,
|
||||
@@ -521,6 +528,14 @@ bool WvCdmTestBase::Initialize(int argc, const char* const argv[],
|
||||
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 {
|
||||
@@ -562,266 +577,6 @@ bool WvCdmTestBase::Initialize(int argc, const char* const argv[],
|
||||
return true;
|
||||
}
|
||||
|
||||
TestLicenseHolder::TestLicenseHolder(CdmEngine* cdm_engine)
|
||||
: cdm_engine_(cdm_engine),
|
||||
oemcrypto_api_(0),
|
||||
session_opened_(false),
|
||||
// Keys are initialized with simple values, and the correct size:
|
||||
derived_mac_key_server_(MAC_KEY_SIZE, 'a'),
|
||||
derived_mac_key_client_(MAC_KEY_SIZE, 'b'),
|
||||
mac_key_server_(MAC_KEY_SIZE, 'c'),
|
||||
mac_key_client_(MAC_KEY_SIZE, 'd'),
|
||||
enc_key_(CONTENT_KEY_SIZE, 'e'),
|
||||
session_key_(CONTENT_KEY_SIZE, 'f') {}
|
||||
|
||||
TestLicenseHolder::~TestLicenseHolder() { CloseSession(); }
|
||||
|
||||
void TestLicenseHolder::OpenSession(const std::string& key_system) {
|
||||
CdmResponseType status =
|
||||
cdm_engine_->OpenSession(key_system, nullptr, nullptr, &session_id_);
|
||||
ASSERT_EQ(status, NO_ERROR);
|
||||
ASSERT_NE("", session_id_) << "Could not open CDM session.";
|
||||
ASSERT_TRUE(cdm_engine_->IsOpenSession(session_id_));
|
||||
session_opened_ = true;
|
||||
}
|
||||
|
||||
void TestLicenseHolder::CloseSession() {
|
||||
if (session_opened_) {
|
||||
cdm_engine_->CloseSession(session_id_);
|
||||
session_opened_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void TestLicenseHolder::GenerateKeyRequest(
|
||||
const std::string& key_id, const std::string& init_data_type_string) {
|
||||
ASSERT_TRUE(session_opened_);
|
||||
CdmAppParameterMap app_parameters;
|
||||
CdmKeySetId key_set_id;
|
||||
InitializationData init_data(init_data_type_string, key_id);
|
||||
if (wvutil::g_cutoff >= wvutil::CDM_LOG_DEBUG) init_data.DumpToLogs();
|
||||
CdmKeyRequest key_request;
|
||||
CdmResponseType result = cdm_engine_->GenerateKeyRequest(
|
||||
session_id_, key_set_id, init_data, kLicenseTypeStreaming, app_parameters,
|
||||
&key_request);
|
||||
EXPECT_EQ(KEY_MESSAGE, result);
|
||||
signed_license_request_data_ = key_request.message;
|
||||
EXPECT_EQ(kKeyRequestTypeInitial, key_request.type);
|
||||
}
|
||||
|
||||
void TestLicenseHolder::CreateDefaultLicense() {
|
||||
video_widevine::SignedMessage signed_message;
|
||||
EXPECT_TRUE(signed_message.ParseFromString(signed_license_request_data_));
|
||||
license_request_data_ = signed_message.msg();
|
||||
video_widevine::LicenseRequest license_request;
|
||||
EXPECT_TRUE(license_request.ParseFromString(license_request_data_));
|
||||
video_widevine::ClientIdentification client_id = license_request.client_id();
|
||||
|
||||
EXPECT_EQ(
|
||||
video_widevine::ClientIdentification_TokenType_DRM_DEVICE_CERTIFICATE,
|
||||
client_id.type());
|
||||
|
||||
oemcrypto_api_ =
|
||||
(client_id.has_client_capabilities() &&
|
||||
client_id.client_capabilities().has_oem_crypto_api_version())
|
||||
? client_id.client_capabilities().oem_crypto_api_version()
|
||||
: 0;
|
||||
|
||||
std::string signed_buffer = license_request_data_;
|
||||
std::string core_message = signed_message.has_oemcrypto_core_message()
|
||||
? signed_message.oemcrypto_core_message()
|
||||
: "";
|
||||
if (oemcrypto_api_ >= 17) {
|
||||
signed_buffer = signed_message.oemcrypto_core_message() + signed_buffer;
|
||||
}
|
||||
|
||||
// Extract the RSA key from the DRM certificate.
|
||||
std::string token = client_id.token();
|
||||
video_widevine::SignedDrmCertificate signed_drm_cert;
|
||||
EXPECT_TRUE(signed_drm_cert.ParseFromString(token));
|
||||
video_widevine::DrmCertificate drm_cert;
|
||||
EXPECT_TRUE(drm_cert.ParseFromString(signed_drm_cert.drm_certificate()));
|
||||
EXPECT_TRUE(rsa_key_.Init(drm_cert.public_key()));
|
||||
EXPECT_TRUE(
|
||||
rsa_key_.VerifySignature(signed_buffer, signed_message.signature()));
|
||||
|
||||
DeriveKeysFromSessionKey();
|
||||
|
||||
video_widevine::LicenseIdentification* license_id = license()->mutable_id();
|
||||
license_id->set_request_id("TestCase");
|
||||
license_id->set_session_id(session_id_);
|
||||
license_id->set_type(video_widevine::STREAMING);
|
||||
license_id->set_version(0);
|
||||
|
||||
::video_widevine::License_Policy* policy = license()->mutable_policy();
|
||||
policy->set_can_play(true);
|
||||
policy->set_can_persist(false);
|
||||
policy->set_can_renew(false);
|
||||
policy->set_playback_duration_seconds(0);
|
||||
policy->set_license_duration_seconds(0);
|
||||
|
||||
AddMacKey();
|
||||
}
|
||||
|
||||
void TestLicenseHolder::AddMacKey() {
|
||||
video_widevine::License_KeyContainer* key_container = license()->add_key();
|
||||
std::vector<uint8_t> iv(KEY_IV_SIZE, 'v');
|
||||
std::string iv_s(iv.begin(), iv.end());
|
||||
key_container->set_iv(iv_s);
|
||||
key_container->set_type(video_widevine::License_KeyContainer_KeyType_SIGNING);
|
||||
|
||||
// Combine server and client mac keys.
|
||||
std::vector<uint8_t> keys(mac_key_server_);
|
||||
keys.insert(keys.end(), mac_key_client_.begin(), mac_key_client_.end());
|
||||
std::string encrypted_keys =
|
||||
WvCdmTestBase::Aes128CbcEncrypt(enc_key_, keys, iv);
|
||||
key_container->set_key(encrypted_keys);
|
||||
}
|
||||
|
||||
video_widevine::License_KeyContainer* TestLicenseHolder::AddKey(
|
||||
const KeyId& key_id, const std::vector<uint8_t>& key_data,
|
||||
const wvoec::KeyControlBlock& block_in) {
|
||||
video_widevine::License_KeyContainer* key_container = license()->add_key();
|
||||
wvoec::KeyControlBlock block = block_in;
|
||||
if (block.verification[0] == 0) {
|
||||
block.verification[0] = 'k';
|
||||
block.verification[1] = 'c';
|
||||
block.verification[2] = '1';
|
||||
// This will work until oemcrypto api 20.
|
||||
block.verification[3] = '0' + wvoec::global_features.api_version - 10;
|
||||
}
|
||||
key_container->set_id(key_id);
|
||||
key_container->set_type(video_widevine::License_KeyContainer_KeyType_CONTENT);
|
||||
key_container->set_level(
|
||||
video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_CRYPTO);
|
||||
|
||||
std::vector<uint8_t> iv(KEY_IV_SIZE, 'v');
|
||||
std::string iv_s(iv.begin(), iv.end());
|
||||
key_container->set_iv(iv_s);
|
||||
|
||||
std::string encrypted_key_data =
|
||||
WvCdmTestBase::Aes128CbcEncrypt(enc_key_, key_data, iv);
|
||||
// TODO(b/111069024): remove this!
|
||||
std::string padding(CONTENT_KEY_SIZE, '-');
|
||||
key_container->set_key(encrypted_key_data + padding);
|
||||
|
||||
// TODO(b/192700112): This mock license server should parse the core message
|
||||
// request, and only encrypt the key control block if the API is greater than
|
||||
// or equal to 16.5. For now, we can pass tests on the v17 branch by checking
|
||||
// against v17.0.
|
||||
if (oemcrypto_api_ >= 17) {
|
||||
std::string block_s(reinterpret_cast<const char*>(&block),
|
||||
reinterpret_cast<const char*>(&block) + sizeof(block));
|
||||
key_container->mutable_key_control()->set_key_control_block(block_s);
|
||||
} else {
|
||||
std::vector<uint8_t> block_v(
|
||||
reinterpret_cast<const uint8_t*>(&block),
|
||||
reinterpret_cast<const uint8_t*>(&block) + sizeof(block));
|
||||
std::vector<uint8_t> block_iv(KEY_IV_SIZE, 'w');
|
||||
std::string block_iv_s(block_iv.begin(), block_iv.end());
|
||||
std::string encrypted_block =
|
||||
WvCdmTestBase::Aes128CbcEncrypt(key_data, block_v, block_iv);
|
||||
key_container->mutable_key_control()->set_iv(block_iv_s);
|
||||
key_container->mutable_key_control()->set_key_control_block(
|
||||
encrypted_block);
|
||||
}
|
||||
return key_container;
|
||||
}
|
||||
|
||||
void TestLicenseHolder::SignAndLoadLicense() {
|
||||
#if 0 // Need to turn off protobuf_lite to use this.
|
||||
LOGV("License = %s\n", license_.DebugString().c_str());
|
||||
#endif
|
||||
std::string license_data;
|
||||
license_.SerializeToString(&license_data);
|
||||
|
||||
std::string signature =
|
||||
WvCdmTestBase::SignHMAC(license_data, derived_mac_key_server_);
|
||||
|
||||
std::string session_key_s(session_key_.begin(), session_key_.end());
|
||||
std::string encrypted_session_key;
|
||||
EXPECT_TRUE(rsa_key_.Encrypt(session_key_s, &encrypted_session_key));
|
||||
video_widevine::SignedMessage signed_response;
|
||||
signed_response.set_msg(license_data);
|
||||
signed_response.set_type(video_widevine::SignedMessage_MessageType_LICENSE);
|
||||
signed_response.set_session_key(encrypted_session_key);
|
||||
signed_response.set_signature(signature);
|
||||
|
||||
std::string response_data;
|
||||
signed_response.SerializeToString(&response_data);
|
||||
|
||||
CdmKeySetId key_set_id;
|
||||
CdmLicenseType license_type; // Required for AddKey. Result value ignored.
|
||||
EXPECT_EQ(KEY_ADDED, cdm_engine_->AddKey(session_id_, response_data,
|
||||
&license_type, &key_set_id));
|
||||
}
|
||||
|
||||
void TestLicenseHolder::DeriveKeysFromSessionKey() {
|
||||
std::string context;
|
||||
GenerateMacContext(license_request_data_, &context);
|
||||
std::vector<uint8_t> mac_key_context(context.begin(), context.end());
|
||||
GenerateEncryptContext(license_request_data_, &context);
|
||||
std::vector<uint8_t> enc_key_context(context.begin(), context.end());
|
||||
|
||||
ASSERT_TRUE(
|
||||
DeriveKey(session_key_, mac_key_context, 1, &derived_mac_key_server_));
|
||||
std::vector<uint8_t> mac_key_part2;
|
||||
ASSERT_TRUE(DeriveKey(session_key_, mac_key_context, 2, &mac_key_part2));
|
||||
derived_mac_key_server_.insert(derived_mac_key_server_.end(),
|
||||
mac_key_part2.begin(), mac_key_part2.end());
|
||||
|
||||
ASSERT_TRUE(
|
||||
DeriveKey(session_key_, mac_key_context, 3, &derived_mac_key_client_));
|
||||
ASSERT_TRUE(DeriveKey(session_key_, mac_key_context, 4, &mac_key_part2));
|
||||
derived_mac_key_client_.insert(derived_mac_key_client_.end(),
|
||||
mac_key_part2.begin(), mac_key_part2.end());
|
||||
|
||||
std::vector<uint8_t> enc_key;
|
||||
ASSERT_TRUE(DeriveKey(session_key_, enc_key_context, 1, &enc_key_));
|
||||
}
|
||||
|
||||
bool TestLicenseHolder::DeriveKey(const std::vector<uint8_t>& key,
|
||||
const std::vector<uint8_t>& context,
|
||||
int counter, std::vector<uint8_t>* out) {
|
||||
if (key.empty() || counter > 4 || context.empty() || out == nullptr) {
|
||||
LOGE("DeriveKey(): bad context");
|
||||
return false;
|
||||
}
|
||||
const EVP_CIPHER* cipher = EVP_aes_128_cbc();
|
||||
CMAC_CTX* cmac_ctx = CMAC_CTX_new();
|
||||
|
||||
if (!cmac_ctx) {
|
||||
LOGE("DeriveKey(): cmac failure");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CMAC_Init(cmac_ctx, &key[0], key.size(), cipher, nullptr)) {
|
||||
LOGE("DeriveKey(): CMAC_Init");
|
||||
CMAC_CTX_free(cmac_ctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> message;
|
||||
message.push_back(counter);
|
||||
message.insert(message.end(), context.begin(), context.end());
|
||||
|
||||
if (!CMAC_Update(cmac_ctx, &message[0], message.size())) {
|
||||
LOGE("DeriveKey(): CMAC_Update");
|
||||
CMAC_CTX_free(cmac_ctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t reslen;
|
||||
uint8_t res[128];
|
||||
if (!CMAC_Final(cmac_ctx, res, &reslen)) {
|
||||
LOGE("DeriveKey(): CMAC_Final");
|
||||
CMAC_CTX_free(cmac_ctx);
|
||||
return false;
|
||||
}
|
||||
out->assign(res, res + reslen);
|
||||
CMAC_CTX_free(cmac_ctx);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MakePSSH(const video_widevine::WidevinePsshData& header) {
|
||||
std::string data;
|
||||
header.SerializeToString(&data);
|
||||
|
||||
@@ -108,69 +108,6 @@ class TestCryptoSession : public CryptoSession {
|
||||
CdmResponseType GenerateNonce(uint32_t* nonce) override;
|
||||
};
|
||||
|
||||
// A holder for a license. Users of this class will first open a session with
|
||||
// OpenSession, then generate a key request with GenerateKeyRequest, and then
|
||||
// call CreateDefaultLicense to create a bare-bones license with no keys in it.
|
||||
// The user may then access the license to adjust the policy, or use AddKey to
|
||||
// add keys to the license. The license is then loaded via SignAndLoadLicense.
|
||||
class TestLicenseHolder {
|
||||
public:
|
||||
// cdm_engine must exist and outlive the TestLicenseHolder.
|
||||
TestLicenseHolder(CdmEngine* cdm_engine);
|
||||
~TestLicenseHolder();
|
||||
// Caller must ensure device already provisioned.
|
||||
void OpenSession(const std::string& key_system);
|
||||
void CloseSession();
|
||||
// Use the cdm_engine to generate a key request in the session. This should
|
||||
// be called after OpenSession. This saves the signed license request, so
|
||||
// that the DRM certificate can be extracted in CreateDefaultLicense.
|
||||
void GenerateKeyRequest(const std::string& key_id,
|
||||
const std::string& init_data_type_string);
|
||||
// Create a bare-bones license from the license request. After this, the user
|
||||
// may access and modify the license using license() below.
|
||||
void CreateDefaultLicense();
|
||||
// Sign the license using the DRM certificate's RSA key. Then the license is
|
||||
// passed to the cdm_engine using AddKey. After this, the license is loaded
|
||||
// and the keys may be used.
|
||||
void SignAndLoadLicense();
|
||||
|
||||
// The session id. This is only valid after a call to OpenSession.
|
||||
const std::string& session_id() { return session_id_; }
|
||||
// The license protobuf. This is only valid after CreateDefaultLicense.
|
||||
video_widevine::License* license() { return &license_; };
|
||||
// Add a key with the given key control block and key data.
|
||||
// If the block's verification is empty, it will be set to a valid value.
|
||||
// The key data is encrypted correctly.
|
||||
video_widevine::License_KeyContainer* AddKey(
|
||||
const KeyId& key_id, const std::vector<uint8_t>& key_data,
|
||||
const wvoec::KeyControlBlock& block);
|
||||
|
||||
private:
|
||||
// Helper method to generate mac keys and encryption keys for the license.
|
||||
void DeriveKeysFromSessionKey();
|
||||
// Derive a single mac key or encryption key using CMAC.
|
||||
bool DeriveKey(const std::vector<uint8_t>& key,
|
||||
const std::vector<uint8_t>& context, int counter,
|
||||
std::vector<uint8_t>* out);
|
||||
// Add the mac keys to the license.
|
||||
void AddMacKey();
|
||||
|
||||
CdmEngine* cdm_engine_;
|
||||
uint32_t oemcrypto_api_;
|
||||
std::string signed_license_request_data_;
|
||||
std::string license_request_data_;
|
||||
std::string session_id_;
|
||||
bool session_opened_;
|
||||
RsaPublicKey rsa_key_; // From the DRM Certificate.
|
||||
video_widevine::License license_;
|
||||
std::vector<uint8_t> derived_mac_key_server_;
|
||||
std::vector<uint8_t> derived_mac_key_client_;
|
||||
std::vector<uint8_t> mac_key_server_;
|
||||
std::vector<uint8_t> mac_key_client_;
|
||||
std::vector<uint8_t> enc_key_;
|
||||
std::vector<uint8_t> session_key_;
|
||||
};
|
||||
|
||||
// Given a PSSH data structure, this makes a PSSH string for use in
|
||||
// generating a license request.
|
||||
std::string MakePSSH(const video_widevine::WidevinePsshData& header);
|
||||
|
||||
@@ -28,6 +28,7 @@ LOCAL_SRC_FILES := \
|
||||
../core/test/config_test_env.cpp \
|
||||
../core/test/fake_provisioning_server.cpp \
|
||||
../core/test/http_socket.cpp \
|
||||
../core/test/license_holder.cpp \
|
||||
../core/test/license_request.cpp \
|
||||
../core/test/test_base.cpp \
|
||||
../core/test/test_printers.cpp \
|
||||
|
||||
@@ -20,10 +20,15 @@ CORE_UTIL_EXPORT std::vector<uint8_t> a2b_hex(const std::string& label,
|
||||
const std::string& b);
|
||||
CORE_UTIL_EXPORT std::string a2bs_hex(const std::string& b);
|
||||
|
||||
// Binary to ASCII hex conversion.
|
||||
// Binary to ASCII hex conversion. The default versions limit output to 2k to
|
||||
// protect us from log spam. The unlimited version has no length limit.
|
||||
CORE_UTIL_EXPORT std::string b2a_hex(const std::vector<uint8_t>& b);
|
||||
CORE_UTIL_EXPORT std::string unlimited_b2a_hex(const std::vector<uint8_t>& b);
|
||||
CORE_UTIL_EXPORT std::string b2a_hex(const std::string& b);
|
||||
CORE_UTIL_EXPORT std::string unlimited_b2a_hex(const std::string& b);
|
||||
CORE_UTIL_EXPORT std::string HexEncode(const uint8_t* bytes, size_t size);
|
||||
CORE_UTIL_EXPORT std::string UnlimitedHexEncode(const uint8_t* bytes,
|
||||
size_t size);
|
||||
|
||||
// Base64 encoding/decoding.
|
||||
// Converts binary data into the ASCII Base64 character set and vice
|
||||
|
||||
@@ -201,20 +201,34 @@ std::string b2a_hex(const std::vector<uint8_t>& byte) {
|
||||
return HexEncode(byte.data(), byte.size());
|
||||
}
|
||||
|
||||
std::string unlimited_b2a_hex(const std::vector<uint8_t>& byte) {
|
||||
if (byte.empty()) return "";
|
||||
return UnlimitedHexEncode(byte.data(), byte.size());
|
||||
}
|
||||
|
||||
std::string b2a_hex(const std::string& byte) {
|
||||
if (byte.empty()) return "";
|
||||
return HexEncode(reinterpret_cast<const uint8_t*>(byte.data()),
|
||||
byte.length());
|
||||
}
|
||||
|
||||
std::string unlimited_b2a_hex(const std::string& byte) {
|
||||
if (byte.empty()) return "";
|
||||
return UnlimitedHexEncode(reinterpret_cast<const uint8_t*>(byte.data()),
|
||||
byte.length());
|
||||
}
|
||||
|
||||
std::string HexEncode(const uint8_t* in_buffer, size_t size) {
|
||||
static const char kHexChars[] = "0123456789ABCDEF";
|
||||
if (size == 0) return "";
|
||||
constexpr unsigned int kMaxSafeSize = 2048;
|
||||
if (size > kMaxSafeSize) size = kMaxSafeSize;
|
||||
return UnlimitedHexEncode(in_buffer, size);
|
||||
}
|
||||
|
||||
std::string UnlimitedHexEncode(const uint8_t* in_buffer, size_t size) {
|
||||
static const char kHexChars[] = "0123456789ABCDEF";
|
||||
if (size == 0) return "";
|
||||
// Each input byte creates two output hex characters.
|
||||
std::string out_buffer(size * 2, '\0');
|
||||
|
||||
for (unsigned int i = 0; i < size; ++i) {
|
||||
char byte = in_buffer[i];
|
||||
out_buffer[(i << 1)] = kHexChars[(byte >> 4) & 0xf];
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace wvutil {
|
||||
|
||||
namespace {
|
||||
// A fake clock that only advances when TestSleep::Sleep is called.
|
||||
class FakeClock : public wvcdm::TestSleep::CallBack {
|
||||
class FakeClock : public TestSleep::CallBack {
|
||||
public:
|
||||
FakeClock() {
|
||||
auto now = std::chrono::system_clock().now();
|
||||
@@ -34,7 +34,7 @@ FakeClock* g_fake_clock = nullptr;
|
||||
|
||||
// On devices running a fake OEMCrypto, we can use a fake sleep and fake time.
|
||||
int64_t Clock::GetCurrentTime() {
|
||||
wvcdm::TestSleep::SyncFakeClock();
|
||||
TestSleep::SyncFakeClock();
|
||||
if (g_fake_clock == nullptr) g_fake_clock = new FakeClock();
|
||||
return g_fake_clock->now() / 1000;
|
||||
}
|
||||
|
||||
@@ -58,6 +58,21 @@ void TestSleep::SyncFakeClock() {
|
||||
Sleep(0);
|
||||
}
|
||||
|
||||
void TestSleep::SetFakeClock(int64_t time_seconds) {
|
||||
if (real_sleep_) {
|
||||
LOGE("SetFakeClock when using a real clock. Expect other failures.");
|
||||
}
|
||||
// Delta could be positive or negative. If the fake clock had been initialized
|
||||
// by the current time on a real clock, and then the command line
|
||||
// re-initializes it to 0, then delta is negative.
|
||||
int64_t delta = time_seconds - Clock().GetCurrentTime();
|
||||
if (callback_ != nullptr) {
|
||||
callback_->ElapseTime(delta * 1000);
|
||||
} else {
|
||||
LOGE("Setting fake clock with no callback. This won't work.");
|
||||
}
|
||||
}
|
||||
|
||||
bool TestSleep::RollbackSystemTime(int seconds) {
|
||||
if (real_sleep_) {
|
||||
#ifdef _WIN32
|
||||
|
||||
@@ -41,6 +41,10 @@ class TestSleep {
|
||||
// verify this function does not roll back the clock used by OEMCrypto.
|
||||
static bool RollbackSystemTime(int seconds);
|
||||
|
||||
// Set the system clock to the specified time. This is only expected to work
|
||||
// when real_sleep is false.
|
||||
static void SetFakeClock(int64_t time_seconds);
|
||||
|
||||
// Roll the system clock forward to undo all previous calls to
|
||||
// RollBackSystemTime. Returns true on success.
|
||||
static bool ResetRollback() {
|
||||
|
||||
@@ -2116,7 +2116,7 @@ OEMCryptoResult OEMCrypto_QueryKeyControl(OEMCrypto_SESSION session,
|
||||
* 256 bits it will be used for OEMCrypto_Generic_Sign() or
|
||||
* OEMCrypto_Generic_Verify() as specified in the key control block. If the key
|
||||
* will be used for OEMCrypto_Generic_Encrypt() or OEMCrypto_Generic_Decrypt()
|
||||
* then the cipher mode will always be OEMCrypto_CipherMode_CBC. Continue to
|
||||
* then the cipher mode will always be OEMCrypto_CipherMode_CBCS. Continue to
|
||||
* use this key for this session until OEMCrypto_SelectKey() is called again,
|
||||
* or until OEMCrypto_CloseSession() is called.
|
||||
*
|
||||
@@ -2292,15 +2292,15 @@ OEMCryptoResult OEMCrypto_SelectKey(OEMCrypto_SESSION session,
|
||||
* 'cbcs'. Starting with v16, OEMCrypto only supports 'cenc' and 'cbcs'. The
|
||||
* schemes 'cens' and 'cbc1' are not supported.
|
||||
*
|
||||
* The decryption mode, either OEMCrypto_CipherMode_CTR or
|
||||
* OEMCrypto_CipherMode_CBC, was already specified in the call to
|
||||
* The decryption mode, either OEMCrypto_CipherMode_CENC or
|
||||
* OEMCrypto_CipherMode_CBCS, was already specified in the call to
|
||||
* OEMCrypto_SelectKey(). The encryption pattern is specified by the fields in
|
||||
* the parameter pattern. A description of partial encryption patterns for
|
||||
* 'cbcs' can be found in the ISO-CENC standard, section 10.4.
|
||||
*
|
||||
* 'cenc' SCHEME:
|
||||
*
|
||||
* The 'cenc' scheme is OEMCrypto_CipherMode_CTR without an encryption
|
||||
* The 'cenc' scheme is OEMCrypto_CipherMode_CENC without an encryption
|
||||
* pattern. All the bytes in the encrypted portion of each subsample are
|
||||
* encrypted. In the pattern parameter, both the encrypt and skip fields will
|
||||
* be zero.
|
||||
@@ -2323,7 +2323,7 @@ OEMCryptoResult OEMCrypto_SelectKey(OEMCrypto_SESSION session,
|
||||
*
|
||||
* 'cbcs' SCHEME:
|
||||
*
|
||||
* The 'cbcs' scheme is OEMCrypto_CipherMode_CBC with an encryption pattern.
|
||||
* The 'cbcs' scheme is OEMCrypto_CipherMode_CBCS with an encryption pattern.
|
||||
* Only some of the bytes in the encrypted portion of each subsample are
|
||||
* encrypted. In the pattern parameter, the encrypt and skip fields will
|
||||
* usually be non-zero. This mode allows devices to decrypt FMP4 HLS content,
|
||||
@@ -4811,9 +4811,11 @@ OEMCryptoResult OEMCrypto_GetBootCertificateChain(
|
||||
* @param[in,out] public_key_size: on input, size of the caller's public_key
|
||||
* buffer. On output, the number of bytes written into the buffer.
|
||||
* @param[out] public_key_signature: pointer to the buffer that receives the
|
||||
* signature of the public key. If an OEM private key is unavailable, it is
|
||||
* signed by the device private key; otherwise is signed by the OEM private
|
||||
* key.
|
||||
* signature of the public key.
|
||||
* If an OEM private key is unavailable: it is signed by the device private
|
||||
* key. The signature must be in COSE_SIGN1 format as specified in RFC 8152.
|
||||
* If an OEM private key is available: it is signed by the OEM private key.
|
||||
* The signature must be raw signature bytes.
|
||||
* @param[in,out] public_key_signature_size: on input, size of the caller's
|
||||
* public_key_signature buffer. On output, the number of bytes written into
|
||||
* the buffer.
|
||||
|
||||
@@ -1 +1 @@
|
||||
../odk/include/OEMCryptoCENCCommon.h
|
||||
../../oemcrypto/odk/include/OEMCryptoCENCCommon.h
|
||||
@@ -30,13 +30,13 @@ struct CoreMessageFeatures {
|
||||
uint32_t maximum_major_version = 17;
|
||||
uint32_t maximum_minor_version = 0;
|
||||
|
||||
bool operator==(const CoreMessageFeatures &other) const;
|
||||
bool operator!=(const CoreMessageFeatures &other) const {
|
||||
bool operator==(const CoreMessageFeatures& other) const;
|
||||
bool operator!=(const CoreMessageFeatures& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
||||
|
||||
std::ostream &operator<<(std::ostream &os, const CoreMessageFeatures &features);
|
||||
std::ostream& operator<<(std::ostream& os, const CoreMessageFeatures& features);
|
||||
|
||||
} // namespace features
|
||||
} // namespace oemcrypto_core_message
|
||||
|
||||
@@ -35,10 +35,10 @@ extern "C" {
|
||||
*/
|
||||
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#define ALIGNED __attribute__((aligned))
|
||||
# define ALIGNED __attribute__((aligned))
|
||||
#else
|
||||
#define ALIGNED
|
||||
#error ODK_Message must be aligned to the maximum useful alignment of the \
|
||||
# define ALIGNED
|
||||
# error ODK_Message must be aligned to the maximum useful alignment of the \
|
||||
machine you are compiling for. Define the ALIGNED macro accordingly.
|
||||
#endif
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ extern "C" {
|
||||
#define ODK_MINOR_VERSION 0
|
||||
|
||||
/* ODK Version string. Date changed automatically on each release. */
|
||||
#define ODK_RELEASE_DATE "ODK v17.0 2022-01-24"
|
||||
#define ODK_RELEASE_DATE "ODK v17.0 2022-02-15"
|
||||
|
||||
/* The lowest version number for an ODK message. */
|
||||
#define ODK_FIRST_VERSION 16
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace oemcrypto_core_message {
|
||||
namespace features {
|
||||
const CoreMessageFeatures CoreMessageFeatures::kDefaultFeatures;
|
||||
|
||||
bool CoreMessageFeatures::operator==(const CoreMessageFeatures &other) const {
|
||||
bool CoreMessageFeatures::operator==(const CoreMessageFeatures& other) const {
|
||||
return maximum_major_version == other.maximum_major_version &&
|
||||
maximum_minor_version == other.maximum_minor_version;
|
||||
}
|
||||
@@ -31,8 +31,8 @@ CoreMessageFeatures CoreMessageFeatures::DefaultFeatures(
|
||||
return features;
|
||||
}
|
||||
|
||||
std::ostream &operator<<(std::ostream &os,
|
||||
const CoreMessageFeatures &features) {
|
||||
std::ostream& operator<<(std::ostream& os,
|
||||
const CoreMessageFeatures& features) {
|
||||
return os << "v" << features.maximum_major_version << "."
|
||||
<< features.maximum_minor_version;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
// License Agreement.
|
||||
#include "fuzzing/odk_fuzz_helper.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "odk.h"
|
||||
|
||||
namespace oemcrypto_core_message {
|
||||
|
||||
@@ -14,7 +14,7 @@ void FreeOutputBuffers(OEMCrypto_SESSION session_id,
|
||||
int* secure_fd) {
|
||||
switch (output_descriptor.type) {
|
||||
case OEMCrypto_BufferType_Clear: {
|
||||
delete[] output_descriptor.buffer.clear.address;
|
||||
delete[] output_descriptor.buffer.clear.clear_buffer;
|
||||
break;
|
||||
}
|
||||
case OEMCrypto_BufferType_Secure: {
|
||||
@@ -32,7 +32,7 @@ bool InitializeOutputBuffers(OEMCrypto_SESSION session_id,
|
||||
int* secure_fd, size_t input_buffer_size) {
|
||||
switch (output_descriptor.type) {
|
||||
case OEMCrypto_BufferType_Clear: {
|
||||
output_descriptor.buffer.clear.address =
|
||||
output_descriptor.buffer.clear.clear_buffer =
|
||||
new OEMCrypto_SharedMemory[input_buffer_size];
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||
license_api_fuzz.LoadLicense();
|
||||
OEMCrypto_SelectKey(session->session_id(), session->license().keys[0].key_id,
|
||||
session->license().keys[0].key_id_length,
|
||||
OEMCrypto_CipherMode_CTR);
|
||||
OEMCrypto_CipherMode_CENC);
|
||||
// Calculate signature for in buffer.
|
||||
size_t signature_length = 0;
|
||||
OEMCrypto_Generic_Sign(session->session_id(), in_buffer.data(),
|
||||
|
||||
@@ -749,9 +749,15 @@ void LicenseRoundTrip::FillCoreResponseSubstrings() {
|
||||
core_response_.key_array[i].key_data =
|
||||
FindSubstring(response_data_.keys[i].key_data,
|
||||
response_data_.keys[i].key_data_length);
|
||||
core_response_.key_array[i].key_control_iv =
|
||||
FindSubstring(response_data_.keys[i].control_iv,
|
||||
sizeof(response_data_.keys[i].control_iv));
|
||||
if (core_request().api_major_version < kClearControlBlockAPIMajor ||
|
||||
(core_request().api_major_version == kClearControlBlockAPIMajor &&
|
||||
core_request().api_minor_version < kClearControlBlockAPIMinor)) {
|
||||
core_response_.key_array[i].key_control_iv =
|
||||
FindSubstring(response_data_.keys[i].control_iv,
|
||||
sizeof(response_data_.keys[i].control_iv));
|
||||
} else {
|
||||
core_response_.key_array[i].key_control_iv = FindSubstring(nullptr, 0);
|
||||
}
|
||||
core_response_.key_array[i].key_control =
|
||||
FindSubstring(&response_data_.keys[i].control,
|
||||
sizeof(response_data_.keys[i].control));
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -172,6 +172,7 @@ const size_t kMaxConcurrentSession[] = { 10, 20, 30, 40};
|
||||
const size_t kMaxKeysPerSession[] = { 4, 20, 20, 30};
|
||||
const size_t kMaxTotalKeys[] = { 16, 40, 80, 90};
|
||||
const size_t kLargeMessageSize[] = { 8*KiB, 8*KiB, 16*KiB, 32*KiB};
|
||||
const size_t kMaxTotalDRMPrivateKeys[] = { 2, 4, 6, 8};
|
||||
// Note: Frame rate and simultaneous playback are specified by resource rating,
|
||||
// but are tested at the system level, so there are no unit tests for frame
|
||||
// rate. Similarly, number of subsamples for AV1
|
||||
@@ -265,7 +266,7 @@ TEST_F(OEMCryptoClientTest, FreeUnallocatedSecureBufferNoFailure) {
|
||||
*/
|
||||
TEST_F(OEMCryptoClientTest, VersionNumber) {
|
||||
const std::string log_message =
|
||||
"OEMCrypto unit tests for API 17.0. Tests last updated 2021-12-03";
|
||||
"OEMCrypto unit tests for API 17.0. Tests last updated 2022-02-18";
|
||||
cout << " " << log_message << "\n";
|
||||
cout << " "
|
||||
<< "These tests are part of Android T."
|
||||
@@ -1569,59 +1570,6 @@ class OEMCryptoSessionTests : public OEMCryptoClientTest {
|
||||
license_messages.EncryptAndSignResponse();
|
||||
return license_messages.LoadResponse();
|
||||
}
|
||||
|
||||
void TestLoadLicenseForHugeBufferLengths(
|
||||
const std::function<void(size_t, LicenseRoundTrip*)> f, bool check_status,
|
||||
bool update_core_message_substring_values) {
|
||||
auto oemcrypto_function = [&](size_t message_length) {
|
||||
Session s;
|
||||
LicenseRoundTrip license_messages(&s);
|
||||
s.open();
|
||||
InstallTestRSAKey(&s);
|
||||
bool verify_keys_loaded = true;
|
||||
license_messages.SignAndVerifyRequest();
|
||||
license_messages.CreateDefaultResponse();
|
||||
if (update_core_message_substring_values) {
|
||||
// Make the license message big enough so that updated core message
|
||||
// substring offset and length values from tests are still able to read
|
||||
// data from license_message buffer rather than reading some garbage
|
||||
// data.
|
||||
license_messages.set_message_size(
|
||||
sizeof(license_messages.response_data()) + message_length);
|
||||
}
|
||||
f(message_length, &license_messages);
|
||||
if (update_core_message_substring_values) {
|
||||
// We will be updating offset for these tests, which will cause verify
|
||||
// keys to fail with an assertion. Hence skipping verification.
|
||||
verify_keys_loaded = false;
|
||||
}
|
||||
license_messages.EncryptAndSignResponse();
|
||||
OEMCryptoResult result =
|
||||
license_messages.LoadResponse(&s, verify_keys_loaded);
|
||||
s.close();
|
||||
return result;
|
||||
};
|
||||
TestHugeLengthDoesNotCrashAPI(oemcrypto_function, check_status);
|
||||
}
|
||||
|
||||
void TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths(
|
||||
const std::function<void(size_t, LicenseRoundTrip*)> f) {
|
||||
Session s;
|
||||
LicenseRoundTrip license_messages(&s);
|
||||
s.open();
|
||||
InstallTestRSAKey(&s);
|
||||
license_messages.SignAndVerifyRequest();
|
||||
license_messages.CreateDefaultResponse();
|
||||
size_t message_length = sizeof(license_messages.response_data());
|
||||
f(message_length, &license_messages);
|
||||
license_messages.EncryptAndSignResponse();
|
||||
OEMCryptoResult result = license_messages.LoadResponse();
|
||||
s.close();
|
||||
// Verifying error is not due to signature failure which can be caused due
|
||||
// to test code.
|
||||
ASSERT_NE(OEMCrypto_ERROR_SIGNATURE_FAILURE, result);
|
||||
ASSERT_NE(OEMCrypto_SUCCESS, result);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(OEMCryptoSessionTests,
|
||||
@@ -1695,6 +1643,77 @@ TEST_F(OEMCryptoSessionTests,
|
||||
!kCheckStatus);
|
||||
}
|
||||
|
||||
class OEMCryptoLicenseOverflowTest : public OEMCryptoSessionTests,
|
||||
public WithParamInterface<uint32_t> {
|
||||
public:
|
||||
OEMCryptoLicenseOverflowTest() : license_api_version_(kCurrentAPI) {}
|
||||
|
||||
void SetUp() override {
|
||||
OEMCryptoSessionTests::SetUp();
|
||||
license_api_version_ = GetParam();
|
||||
}
|
||||
|
||||
void TearDown() override { OEMCryptoSessionTests::TearDown(); }
|
||||
|
||||
void TestLoadLicenseForHugeBufferLengths(
|
||||
const std::function<void(size_t, LicenseRoundTrip*)> f, bool check_status,
|
||||
bool update_core_message_substring_values) {
|
||||
auto oemcrypto_function = [&](size_t message_length) {
|
||||
Session s;
|
||||
LicenseRoundTrip license_messages(&s);
|
||||
license_messages.set_api_version(license_api_version_);
|
||||
s.open();
|
||||
InstallTestRSAKey(&s);
|
||||
bool verify_keys_loaded = true;
|
||||
license_messages.SignAndVerifyRequest();
|
||||
license_messages.CreateDefaultResponse();
|
||||
if (update_core_message_substring_values) {
|
||||
// Make the license message big enough so that updated core message
|
||||
// substring offset and length values from tests are still able to read
|
||||
// data from license_message buffer rather than reading some garbage
|
||||
// data.
|
||||
license_messages.set_message_size(
|
||||
sizeof(license_messages.response_data()) + message_length);
|
||||
}
|
||||
f(message_length, &license_messages);
|
||||
if (update_core_message_substring_values) {
|
||||
// We will be updating offset for these tests, which will cause verify
|
||||
// keys to fail with an assertion. Hence skipping verification.
|
||||
verify_keys_loaded = false;
|
||||
}
|
||||
license_messages.EncryptAndSignResponse();
|
||||
OEMCryptoResult result =
|
||||
license_messages.LoadResponse(&s, verify_keys_loaded);
|
||||
s.close();
|
||||
return result;
|
||||
};
|
||||
TestHugeLengthDoesNotCrashAPI(oemcrypto_function, check_status);
|
||||
}
|
||||
|
||||
void TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths(
|
||||
const std::function<void(size_t, LicenseRoundTrip*)> f) {
|
||||
Session s;
|
||||
LicenseRoundTrip license_messages(&s);
|
||||
license_messages.set_api_version(license_api_version_);
|
||||
s.open();
|
||||
InstallTestRSAKey(&s);
|
||||
license_messages.SignAndVerifyRequest();
|
||||
license_messages.CreateDefaultResponse();
|
||||
size_t message_length = sizeof(license_messages.response_data());
|
||||
OEMCryptoResult result = license_messages.LoadResponse();
|
||||
f(message_length, &license_messages);
|
||||
license_messages.EncryptAndSignResponse();
|
||||
s.close();
|
||||
// Verifying error is not due to signature failure which can be caused due
|
||||
// to test code.
|
||||
ASSERT_NE(OEMCrypto_ERROR_SIGNATURE_FAILURE, result);
|
||||
ASSERT_NE(OEMCrypto_SUCCESS, result);
|
||||
}
|
||||
|
||||
protected:
|
||||
uint32_t license_api_version_;
|
||||
};
|
||||
|
||||
// This class is for testing a single license with the default API version
|
||||
// of 16. Used for buffer overflow tests.
|
||||
class OEMCryptoMemoryLicenseTest : public OEMCryptoLicenseTestAPI16 {
|
||||
@@ -3424,7 +3443,7 @@ TEST_F(OEMCryptoSessionTests,
|
||||
TestHugeLengthDoesNotCrashAPI(oemcrypto_function, !kCheckStatus);
|
||||
}
|
||||
|
||||
TEST_F(OEMCryptoSessionTests,
|
||||
TEST_P(OEMCryptoLicenseOverflowTest,
|
||||
OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringKeyIdLength) {
|
||||
TestLoadLicenseForHugeBufferLengths(
|
||||
[](size_t length, LicenseRoundTrip* license_messages) {
|
||||
@@ -3434,7 +3453,7 @@ TEST_F(OEMCryptoSessionTests,
|
||||
!kCheckStatus, kUpdateCoreMessageSubstringValues);
|
||||
}
|
||||
|
||||
TEST_F(OEMCryptoSessionTests,
|
||||
TEST_P(OEMCryptoLicenseOverflowTest,
|
||||
OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringKeyIdOffset) {
|
||||
TestLoadLicenseForHugeBufferLengths(
|
||||
[](size_t offset, LicenseRoundTrip* license_messages) {
|
||||
@@ -3443,7 +3462,7 @@ TEST_F(OEMCryptoSessionTests,
|
||||
!kCheckStatus, kUpdateCoreMessageSubstringValues);
|
||||
}
|
||||
|
||||
TEST_F(OEMCryptoSessionTests,
|
||||
TEST_P(OEMCryptoLicenseOverflowTest,
|
||||
OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringKeyIdLength) {
|
||||
TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths(
|
||||
[](size_t response_message_length, LicenseRoundTrip* license_messages) {
|
||||
@@ -3452,7 +3471,7 @@ TEST_F(OEMCryptoSessionTests,
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(OEMCryptoSessionTests,
|
||||
TEST_P(OEMCryptoLicenseOverflowTest,
|
||||
OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringKeyIdOffset) {
|
||||
TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths(
|
||||
[](size_t response_message_length, LicenseRoundTrip* license_messages) {
|
||||
@@ -3461,7 +3480,7 @@ TEST_F(OEMCryptoSessionTests,
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(OEMCryptoSessionTests,
|
||||
TEST_P(OEMCryptoLicenseOverflowTest,
|
||||
OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringKeyDataIvLength) {
|
||||
TestLoadLicenseForHugeBufferLengths(
|
||||
[](size_t length, LicenseRoundTrip* license_messages) {
|
||||
@@ -3471,7 +3490,7 @@ TEST_F(OEMCryptoSessionTests,
|
||||
!kCheckStatus, kUpdateCoreMessageSubstringValues);
|
||||
}
|
||||
|
||||
TEST_F(OEMCryptoSessionTests,
|
||||
TEST_P(OEMCryptoLicenseOverflowTest,
|
||||
OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringKeyDataIvOffset) {
|
||||
TestLoadLicenseForHugeBufferLengths(
|
||||
[](size_t offset, LicenseRoundTrip* license_messages) {
|
||||
@@ -3481,8 +3500,8 @@ TEST_F(OEMCryptoSessionTests,
|
||||
!kCheckStatus, kUpdateCoreMessageSubstringValues);
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
OEMCryptoSessionTests,
|
||||
TEST_P(
|
||||
OEMCryptoLicenseOverflowTest,
|
||||
OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringKeyDataIvLength) {
|
||||
TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths(
|
||||
[](size_t response_message_length, LicenseRoundTrip* license_messages) {
|
||||
@@ -3492,8 +3511,8 @@ TEST_F(
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
OEMCryptoSessionTests,
|
||||
TEST_P(
|
||||
OEMCryptoLicenseOverflowTest,
|
||||
OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringKeyDataIvOffset) {
|
||||
TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths(
|
||||
[](size_t response_message_length, LicenseRoundTrip* license_messages) {
|
||||
@@ -3503,7 +3522,7 @@ TEST_F(
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(OEMCryptoSessionTests,
|
||||
TEST_P(OEMCryptoLicenseOverflowTest,
|
||||
OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringKeyDataLength) {
|
||||
TestLoadLicenseForHugeBufferLengths(
|
||||
[](size_t length, LicenseRoundTrip* license_messages) {
|
||||
@@ -3513,7 +3532,7 @@ TEST_F(OEMCryptoSessionTests,
|
||||
!kCheckStatus, kUpdateCoreMessageSubstringValues);
|
||||
}
|
||||
|
||||
TEST_F(OEMCryptoSessionTests,
|
||||
TEST_P(OEMCryptoLicenseOverflowTest,
|
||||
OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringKeyDataOffset) {
|
||||
TestLoadLicenseForHugeBufferLengths(
|
||||
[](size_t offset, LicenseRoundTrip* license_messages) {
|
||||
@@ -3522,8 +3541,8 @@ TEST_F(OEMCryptoSessionTests,
|
||||
!kCheckStatus, kUpdateCoreMessageSubstringValues);
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
OEMCryptoSessionTests,
|
||||
TEST_P(
|
||||
OEMCryptoLicenseOverflowTest,
|
||||
OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringKeyDataLength) {
|
||||
TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths(
|
||||
[](size_t response_message_length, LicenseRoundTrip* license_messages) {
|
||||
@@ -3533,8 +3552,8 @@ TEST_F(
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
OEMCryptoSessionTests,
|
||||
TEST_P(
|
||||
OEMCryptoLicenseOverflowTest,
|
||||
OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringKeyDataOffset) {
|
||||
TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths(
|
||||
[](size_t response_message_length, LicenseRoundTrip* license_messages) {
|
||||
@@ -3544,8 +3563,8 @@ TEST_F(
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
OEMCryptoSessionTests,
|
||||
TEST_P(
|
||||
OEMCryptoLicenseOverflowTest,
|
||||
OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringKeyControlIvLength) {
|
||||
TestLoadLicenseForHugeBufferLengths(
|
||||
[](size_t length, LicenseRoundTrip* license_messages) {
|
||||
@@ -3555,8 +3574,8 @@ TEST_F(
|
||||
!kCheckStatus, kUpdateCoreMessageSubstringValues);
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
OEMCryptoSessionTests,
|
||||
TEST_P(
|
||||
OEMCryptoLicenseOverflowTest,
|
||||
OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringKeyControlIvOffset) {
|
||||
TestLoadLicenseForHugeBufferLengths(
|
||||
[](size_t offset, LicenseRoundTrip* license_messages) {
|
||||
@@ -3566,8 +3585,8 @@ TEST_F(
|
||||
!kCheckStatus, kUpdateCoreMessageSubstringValues);
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
OEMCryptoSessionTests,
|
||||
TEST_P(
|
||||
OEMCryptoLicenseOverflowTest,
|
||||
OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringKeyControlIvLength) {
|
||||
TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths(
|
||||
[](size_t response_message_length, LicenseRoundTrip* license_messages) {
|
||||
@@ -3578,8 +3597,8 @@ TEST_F(
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
OEMCryptoSessionTests,
|
||||
TEST_P(
|
||||
OEMCryptoLicenseOverflowTest,
|
||||
OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringKeyControlIvOffset) {
|
||||
TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths(
|
||||
[](size_t response_message_length, LicenseRoundTrip* license_messages) {
|
||||
@@ -3590,7 +3609,7 @@ TEST_F(
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(OEMCryptoSessionTests,
|
||||
TEST_P(OEMCryptoLicenseOverflowTest,
|
||||
OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringKeyControlLength) {
|
||||
TestLoadLicenseForHugeBufferLengths(
|
||||
[](size_t length, LicenseRoundTrip* license_messages) {
|
||||
@@ -3600,7 +3619,7 @@ TEST_F(OEMCryptoSessionTests,
|
||||
!kCheckStatus, kUpdateCoreMessageSubstringValues);
|
||||
}
|
||||
|
||||
TEST_F(OEMCryptoSessionTests,
|
||||
TEST_P(OEMCryptoLicenseOverflowTest,
|
||||
OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringKeyControlOffset) {
|
||||
TestLoadLicenseForHugeBufferLengths(
|
||||
[](size_t offset, LicenseRoundTrip* license_messages) {
|
||||
@@ -3610,8 +3629,8 @@ TEST_F(OEMCryptoSessionTests,
|
||||
!kCheckStatus, kUpdateCoreMessageSubstringValues);
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
OEMCryptoSessionTests,
|
||||
TEST_P(
|
||||
OEMCryptoLicenseOverflowTest,
|
||||
OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringKeyControlLength) {
|
||||
TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths(
|
||||
[](size_t response_message_length, LicenseRoundTrip* license_messages) {
|
||||
@@ -3621,8 +3640,8 @@ TEST_F(
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
OEMCryptoSessionTests,
|
||||
TEST_P(
|
||||
OEMCryptoLicenseOverflowTest,
|
||||
OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringKeyControlOffset) {
|
||||
TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths(
|
||||
[](size_t response_message_length, LicenseRoundTrip* license_messages) {
|
||||
@@ -3632,7 +3651,7 @@ TEST_F(
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(OEMCryptoSessionTests,
|
||||
TEST_P(OEMCryptoLicenseOverflowTest,
|
||||
OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringEncMacKeyIvLength) {
|
||||
TestLoadLicenseForHugeBufferLengths(
|
||||
[](size_t length, LicenseRoundTrip* license_messages) {
|
||||
@@ -3641,7 +3660,7 @@ TEST_F(OEMCryptoSessionTests,
|
||||
!kCheckStatus, kUpdateCoreMessageSubstringValues);
|
||||
}
|
||||
|
||||
TEST_F(OEMCryptoSessionTests,
|
||||
TEST_P(OEMCryptoLicenseOverflowTest,
|
||||
OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringEncMacKeyIvOffset) {
|
||||
TestLoadLicenseForHugeBufferLengths(
|
||||
[](size_t offset, LicenseRoundTrip* license_messages) {
|
||||
@@ -3650,8 +3669,8 @@ TEST_F(OEMCryptoSessionTests,
|
||||
!kCheckStatus, kUpdateCoreMessageSubstringValues);
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
OEMCryptoSessionTests,
|
||||
TEST_P(
|
||||
OEMCryptoLicenseOverflowTest,
|
||||
OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringEncMacKeyIvLength) {
|
||||
TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths(
|
||||
[](size_t response_message_length, LicenseRoundTrip* license_messages) {
|
||||
@@ -3662,8 +3681,8 @@ TEST_F(
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
OEMCryptoSessionTests,
|
||||
TEST_P(
|
||||
OEMCryptoLicenseOverflowTest,
|
||||
OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringEncMacKeyIvOffset) {
|
||||
TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths(
|
||||
[](size_t response_message_length, LicenseRoundTrip* license_messages) {
|
||||
@@ -3674,7 +3693,7 @@ TEST_F(
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(OEMCryptoSessionTests,
|
||||
TEST_P(OEMCryptoLicenseOverflowTest,
|
||||
OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringEncMacKeyLength) {
|
||||
TestLoadLicenseForHugeBufferLengths(
|
||||
[](size_t length, LicenseRoundTrip* license_messages) {
|
||||
@@ -3683,7 +3702,7 @@ TEST_F(OEMCryptoSessionTests,
|
||||
!kCheckStatus, kUpdateCoreMessageSubstringValues);
|
||||
}
|
||||
|
||||
TEST_F(OEMCryptoSessionTests,
|
||||
TEST_P(OEMCryptoLicenseOverflowTest,
|
||||
OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringEncMacKeyOffset) {
|
||||
TestLoadLicenseForHugeBufferLengths(
|
||||
[](size_t offset, LicenseRoundTrip* license_messages) {
|
||||
@@ -3692,8 +3711,8 @@ TEST_F(OEMCryptoSessionTests,
|
||||
!kCheckStatus, kUpdateCoreMessageSubstringValues);
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
OEMCryptoSessionTests,
|
||||
TEST_P(
|
||||
OEMCryptoLicenseOverflowTest,
|
||||
OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringEncMacKeyLength) {
|
||||
TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths(
|
||||
[](size_t response_message_length, LicenseRoundTrip* license_messages) {
|
||||
@@ -3702,8 +3721,8 @@ TEST_F(
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
OEMCryptoSessionTests,
|
||||
TEST_P(
|
||||
OEMCryptoLicenseOverflowTest,
|
||||
OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringEncMacKeyOffset) {
|
||||
TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths(
|
||||
[](size_t response_message_length, LicenseRoundTrip* license_messages) {
|
||||
@@ -3712,7 +3731,7 @@ TEST_F(
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(OEMCryptoSessionTests,
|
||||
TEST_P(OEMCryptoLicenseOverflowTest,
|
||||
OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringPstLength) {
|
||||
TestLoadLicenseForHugeBufferLengths(
|
||||
[](size_t length, LicenseRoundTrip* license_messages) {
|
||||
@@ -3721,7 +3740,7 @@ TEST_F(OEMCryptoSessionTests,
|
||||
!kCheckStatus, kUpdateCoreMessageSubstringValues);
|
||||
}
|
||||
|
||||
TEST_F(OEMCryptoSessionTests,
|
||||
TEST_P(OEMCryptoLicenseOverflowTest,
|
||||
OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringPstOffset) {
|
||||
TestLoadLicenseForHugeBufferLengths(
|
||||
[](size_t offset, LicenseRoundTrip* license_messages) {
|
||||
@@ -3730,7 +3749,7 @@ TEST_F(OEMCryptoSessionTests,
|
||||
!kCheckStatus, kUpdateCoreMessageSubstringValues);
|
||||
}
|
||||
|
||||
TEST_F(OEMCryptoSessionTests,
|
||||
TEST_P(OEMCryptoLicenseOverflowTest,
|
||||
OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringPstLength) {
|
||||
TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths(
|
||||
[](size_t response_message_length, LicenseRoundTrip* license_messages) {
|
||||
@@ -3739,7 +3758,7 @@ TEST_F(OEMCryptoSessionTests,
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(OEMCryptoSessionTests,
|
||||
TEST_P(OEMCryptoLicenseOverflowTest,
|
||||
OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringPstOffset) {
|
||||
TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths(
|
||||
[](size_t response_message_length, LicenseRoundTrip* license_messages) {
|
||||
@@ -3748,8 +3767,8 @@ TEST_F(OEMCryptoSessionTests,
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
OEMCryptoSessionTests,
|
||||
TEST_P(
|
||||
OEMCryptoLicenseOverflowTest,
|
||||
OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringSrmRestrictionDataLength) {
|
||||
TestLoadLicenseForHugeBufferLengths(
|
||||
[](size_t length, LicenseRoundTrip* license_messages) {
|
||||
@@ -3758,8 +3777,8 @@ TEST_F(
|
||||
!kCheckStatus, kUpdateCoreMessageSubstringValues);
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
OEMCryptoSessionTests,
|
||||
TEST_P(
|
||||
OEMCryptoLicenseOverflowTest,
|
||||
OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringSrmRestrictionDataOffset) {
|
||||
TestLoadLicenseForHugeBufferLengths(
|
||||
[](size_t offset, LicenseRoundTrip* license_messages) {
|
||||
@@ -3768,8 +3787,8 @@ TEST_F(
|
||||
!kCheckStatus, kUpdateCoreMessageSubstringValues);
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
OEMCryptoSessionTests,
|
||||
TEST_P(
|
||||
OEMCryptoLicenseOverflowTest,
|
||||
OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringSrmRestrictionDataLength) {
|
||||
TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths(
|
||||
[](size_t response_message_length, LicenseRoundTrip* license_messages) {
|
||||
@@ -3780,8 +3799,8 @@ TEST_F(
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
OEMCryptoSessionTests,
|
||||
TEST_P(
|
||||
OEMCryptoLicenseOverflowTest,
|
||||
OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringSrmRestrictionDataOffset) {
|
||||
TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths(
|
||||
[](size_t response_message_length, LicenseRoundTrip* license_messages) {
|
||||
@@ -3792,7 +3811,8 @@ TEST_F(
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(OEMCryptoSessionTests, OEMCryptoMemoryLoadLicenseForHugeResponseLength) {
|
||||
TEST_P(OEMCryptoLicenseOverflowTest,
|
||||
OEMCryptoMemoryLoadLicenseForHugeResponseLength) {
|
||||
TestLoadLicenseForHugeBufferLengths(
|
||||
[](size_t message_size, LicenseRoundTrip* license_messages) {
|
||||
license_messages->set_message_size(message_size);
|
||||
@@ -3800,7 +3820,7 @@ TEST_F(OEMCryptoSessionTests, OEMCryptoMemoryLoadLicenseForHugeResponseLength) {
|
||||
!kCheckStatus, !kUpdateCoreMessageSubstringValues);
|
||||
}
|
||||
|
||||
TEST_F(OEMCryptoSessionTests,
|
||||
TEST_P(OEMCryptoLicenseOverflowTest,
|
||||
OEMCryptoMemoryLoadLicenseForHugeCoreMessageLength) {
|
||||
TestLoadLicenseForHugeBufferLengths(
|
||||
[](size_t message_size, LicenseRoundTrip* license_messages) {
|
||||
@@ -3809,6 +3829,9 @@ TEST_F(OEMCryptoSessionTests,
|
||||
!kCheckStatus, !kUpdateCoreMessageSubstringValues);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(TestAll, OEMCryptoLicenseOverflowTest,
|
||||
Range<uint32_t>(kCurrentAPI - 1, kCurrentAPI + 1));
|
||||
|
||||
TEST_F(OEMCryptoSessionTests, OEMCryptoMemoryLoadRenewalForHugeResponseLength) {
|
||||
auto oemcrypto_function = [&](size_t message_size) {
|
||||
Session s;
|
||||
@@ -5880,6 +5903,55 @@ TEST_F(OEMCryptoLoadsCertificate, TestMultipleRSAKeys) {
|
||||
ASSERT_NO_FATAL_FAILURE(s1.TestDecryptCTR());
|
||||
}
|
||||
|
||||
// This tests the maximum number of DRM private keys that OEMCrypto can load
|
||||
TEST_F(OEMCryptoLoadsCertificate, TestMaxDRMKeys) {
|
||||
const size_t max_total_keys = GetResourceValue(kMaxTotalDRMPrivateKeys);
|
||||
std::vector<std::unique_ptr<Session>> sessions;
|
||||
std::vector<std::unique_ptr<LicenseRoundTrip>> licenses;
|
||||
|
||||
// It should be able to load up to kMaxTotalDRMPrivateKeys keys
|
||||
for (size_t i = 0; i < max_total_keys; i++) {
|
||||
sessions.push_back(std::unique_ptr<Session>(new Session()));
|
||||
licenses.push_back(std::unique_ptr<LicenseRoundTrip>(
|
||||
new LicenseRoundTrip(sessions[i].get())));
|
||||
const size_t key_index = i % kTestRSAPKCS8PrivateKeys_2048.size();
|
||||
encoded_rsa_key_.assign(kTestRSAPKCS8PrivateKeys_2048[key_index].begin(),
|
||||
kTestRSAPKCS8PrivateKeys_2048[key_index].end());
|
||||
ASSERT_NO_FATAL_FAILURE(CreateWrappedRSAKey());
|
||||
ASSERT_NO_FATAL_FAILURE(sessions[i]->open());
|
||||
ASSERT_NO_FATAL_FAILURE(sessions[i]->PreparePublicKey(
|
||||
encoded_rsa_key_.data(), encoded_rsa_key_.size()));
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
sessions[i]->InstallRSASessionTestKey(wrapped_rsa_key_));
|
||||
}
|
||||
|
||||
// Attempts to load one more key than the kMaxTotalDRMPrivateKeys
|
||||
Session s;
|
||||
encoded_rsa_key_.assign(kTestRSAPKCS8PrivateKeyInfo3_3072,
|
||||
kTestRSAPKCS8PrivateKeyInfo3_3072 +
|
||||
sizeof(kTestRSAPKCS8PrivateKeyInfo3_3072));
|
||||
Session ps;
|
||||
ProvisioningRoundTrip provisioning_messages(&ps, encoded_rsa_key_);
|
||||
provisioning_messages.PrepareSession(keybox_);
|
||||
ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest());
|
||||
ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse());
|
||||
ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse());
|
||||
OEMCryptoResult result = provisioning_messages.LoadResponse();
|
||||
// Key loading is allowed to fail due to resource restriction
|
||||
if (result != OEMCrypto_SUCCESS) {
|
||||
ASSERT_TRUE(result == OEMCrypto_ERROR_INSUFFICIENT_RESOURCES ||
|
||||
result == OEMCrypto_ERROR_TOO_MANY_KEYS);
|
||||
}
|
||||
// Verifies that the DRM keys which are already loaded should still function
|
||||
for (size_t i = 0; i < licenses.size(); i++) {
|
||||
ASSERT_NO_FATAL_FAILURE(licenses[i]->SignAndVerifyRequest());
|
||||
ASSERT_NO_FATAL_FAILURE(licenses[i]->CreateDefaultResponse());
|
||||
ASSERT_NO_FATAL_FAILURE(licenses[i]->EncryptAndSignResponse());
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, licenses[i]->LoadResponse());
|
||||
ASSERT_NO_FATAL_FAILURE(sessions[i]->TestDecryptCTR());
|
||||
}
|
||||
}
|
||||
|
||||
// Devices that load certificates, should at least support RSA 2048 keys.
|
||||
TEST_F(OEMCryptoLoadsCertificate, SupportsCertificatesAPI13) {
|
||||
ASSERT_NE(0u,
|
||||
@@ -10030,7 +10102,7 @@ TEST_P(OEMCryptoUsageTableTest, PSTLargeBuffer) {
|
||||
}
|
||||
|
||||
// Verify that a usage entry with an invalid session cannot be used.
|
||||
TEST_P(OEMCryptoUsageTableTest, UsageEntryWithInvalidSessionAPI17) {
|
||||
TEST_P(OEMCryptoUsageTableTest, UsageEntryWithInvalidSession) {
|
||||
std::string pst("pst");
|
||||
LicenseWithUsageEntry entry;
|
||||
entry.license_messages().set_pst(pst);
|
||||
@@ -10048,6 +10120,13 @@ TEST_P(OEMCryptoUsageTableTest, UsageEntryWithInvalidSessionAPI17) {
|
||||
entry.session().close();
|
||||
ASSERT_EQ(OEMCrypto_ERROR_INVALID_SESSION,
|
||||
OEMCrypto_MoveEntry(entry.session().session_id(), 0));
|
||||
}
|
||||
|
||||
// Verify that a usage entry with an invalid session cannot be used.
|
||||
TEST_P(OEMCryptoUsageTableTest, ReuseUsageEntryWithInvalidSessionAPI17) {
|
||||
std::string pst("pst");
|
||||
LicenseWithUsageEntry entry;
|
||||
entry.license_messages().set_pst(pst);
|
||||
|
||||
entry.session().open();
|
||||
ASSERT_NO_FATAL_FAILURE(entry.session().CreateNewUsageEntry());
|
||||
|
||||
Reference in New Issue
Block a user