1818 lines
78 KiB
C++
1818 lines
78 KiB
C++
// Copyright 2023 Google LLC. All Rights Reserved. This file and proprietary
|
|
// source code may only be used and distributed under the Widevine
|
|
// License Agreement.
|
|
//
|
|
|
|
#include "oemcrypto_usage_table_test.h"
|
|
|
|
using ::testing::Range;
|
|
using ::testing::Values;
|
|
|
|
namespace wvoec {
|
|
|
|
/// @addtogroup usage_table
|
|
/// @{
|
|
|
|
// Test that successive calls to PrepAndSignProvisioningRequest only increase
|
|
// the provisioning count in the ODK message
|
|
TEST_F(OEMCryptoSessionTests, Provisioning_IncrementCounterAPI18) {
|
|
if (wvoec::global_features.api_version < 18) {
|
|
GTEST_SKIP() << "Test for versions 18 and up only.";
|
|
}
|
|
// local struct to hold count values from core message
|
|
typedef struct counts {
|
|
uint32_t prov;
|
|
uint32_t lic;
|
|
uint32_t decrypt;
|
|
uint64_t mgn;
|
|
} counts;
|
|
|
|
// prep and sign provisioning2 request, then extract counter values
|
|
auto provision2 = [&](counts* c) {
|
|
Session s;
|
|
ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_);
|
|
provisioning_messages.PrepareSession(keybox_);
|
|
provisioning_messages.SignAndVerifyRequest();
|
|
c->prov =
|
|
provisioning_messages.core_request().counter_info.provisioning_count;
|
|
c->lic = provisioning_messages.core_request().counter_info.license_count;
|
|
c->decrypt =
|
|
provisioning_messages.core_request().counter_info.decrypt_count;
|
|
c->mgn = provisioning_messages.core_request()
|
|
.counter_info.master_generation_number;
|
|
};
|
|
|
|
// prep and sign provisioning4 request, then extract counter values
|
|
auto provision4 = [&](counts* c) {
|
|
// Same as SessionUtil::CreateProv4OEMKey, but we can't extract counter
|
|
// values using that function
|
|
Session s;
|
|
ASSERT_NO_FATAL_FAILURE(s.open());
|
|
Provisioning40RoundTrip provisioning_messages(&s);
|
|
ASSERT_NO_FATAL_FAILURE(provisioning_messages.PrepareSession(true));
|
|
ASSERT_NO_FATAL_FAILURE(s.SetPublicKeyFromSubjectPublicKey(
|
|
provisioning_messages.oem_key_type(),
|
|
provisioning_messages.oem_public_key().data(),
|
|
provisioning_messages.oem_public_key().size()));
|
|
wrapped_oem_key_ = provisioning_messages.wrapped_oem_key();
|
|
oem_public_key_ = provisioning_messages.oem_public_key();
|
|
oem_key_type_ = provisioning_messages.oem_key_type();
|
|
ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest());
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, provisioning_messages.LoadOEMCertResponse());
|
|
c->prov =
|
|
provisioning_messages.core_request().counter_info.provisioning_count;
|
|
c->lic = provisioning_messages.core_request().counter_info.license_count;
|
|
c->decrypt =
|
|
provisioning_messages.core_request().counter_info.decrypt_count;
|
|
c->mgn = provisioning_messages.core_request()
|
|
.counter_info.master_generation_number;
|
|
};
|
|
|
|
if (global_features.provisioning_method == OEMCrypto_OEMCertificate ||
|
|
global_features.provisioning_method == OEMCrypto_DrmCertificate) {
|
|
GTEST_SKIP() << "Provisioning method does not increment prov counter";
|
|
} else if (global_features.provisioning_method == OEMCrypto_Keybox) {
|
|
counts c1, c2;
|
|
provision2(&c1);
|
|
provision2(&c2);
|
|
|
|
ASSERT_TRUE(c2.prov > c1.prov);
|
|
ASSERT_TRUE(c2.lic == c1.lic);
|
|
ASSERT_TRUE(c2.decrypt == c1.decrypt);
|
|
ASSERT_TRUE(c2.mgn == c1.mgn);
|
|
} else if (global_features.provisioning_method ==
|
|
OEMCrypto_BootCertificateChain) {
|
|
counts c1, c2;
|
|
provision4(&c1);
|
|
provision4(&c2);
|
|
|
|
ASSERT_TRUE(c2.prov > c1.prov);
|
|
ASSERT_TRUE(c2.lic == c1.lic);
|
|
ASSERT_TRUE(c2.decrypt == c1.decrypt);
|
|
ASSERT_TRUE(c2.mgn == c1.mgn);
|
|
}
|
|
}
|
|
|
|
// Test that successive calls to PrepAndSignLicenseRequest only increase
|
|
// the license count in the ODK message
|
|
TEST_F(OEMCryptoSessionTests, License_IncrementCounterAPI18) {
|
|
if (wvoec::global_features.api_version < 18) {
|
|
GTEST_SKIP() << "Test for versions 18 and up only.";
|
|
}
|
|
if (OEMCrypto_SecurityLevel() == OEMCrypto_Level3) {
|
|
GTEST_SKIP() << "L3 does not support license counter.";
|
|
}
|
|
Session s;
|
|
s.open();
|
|
LicenseRoundTrip license_messages(&s);
|
|
InstallTestDrmKey(&s);
|
|
ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest());
|
|
uint32_t prov_count1 =
|
|
license_messages.core_request().counter_info.provisioning_count;
|
|
uint32_t lic_count1 =
|
|
license_messages.core_request().counter_info.license_count;
|
|
uint32_t decrypt_count1 =
|
|
license_messages.core_request().counter_info.decrypt_count;
|
|
uint64_t master_generation_number1 =
|
|
license_messages.core_request().counter_info.master_generation_number;
|
|
|
|
Session s2;
|
|
s2.open();
|
|
LicenseRoundTrip license_messages2(&s2);
|
|
InstallTestDrmKey(&s2);
|
|
ASSERT_NO_FATAL_FAILURE(license_messages2.SignAndVerifyRequest());
|
|
uint32_t prov_count2 =
|
|
license_messages2.core_request().counter_info.provisioning_count;
|
|
uint32_t lic_count2 =
|
|
license_messages2.core_request().counter_info.license_count;
|
|
uint32_t decrypt_count2 =
|
|
license_messages2.core_request().counter_info.decrypt_count;
|
|
uint64_t master_generation_number2 =
|
|
license_messages2.core_request().counter_info.master_generation_number;
|
|
|
|
ASSERT_TRUE(prov_count2 == prov_count1);
|
|
ASSERT_TRUE(lic_count2 > lic_count1);
|
|
ASSERT_TRUE(decrypt_count2 == decrypt_count1);
|
|
ASSERT_TRUE(master_generation_number2 == master_generation_number1);
|
|
}
|
|
|
|
// Test that the license request includes the master generation number, and that
|
|
// it is incremented correctly after usage table modification (save offline
|
|
// license) and decrypt. Also test that decrypt count increments.
|
|
TEST_F(OEMCryptoSessionTests, MasterGeneration_IncrementCounterAPI18) {
|
|
if (wvoec::global_features.api_version < 18) {
|
|
GTEST_SKIP() << "Test for versions 18 and up only.";
|
|
}
|
|
if (!OEMCrypto_SupportsUsageTable()) {
|
|
GTEST_SKIP() << "Usage table not supported, so master generation number "
|
|
"does not need to be checked.";
|
|
}
|
|
if (OEMCrypto_SecurityLevel() == OEMCrypto_Level3) {
|
|
GTEST_SKIP() << "L3 does not support license counter.";
|
|
}
|
|
Session s1;
|
|
s1.open();
|
|
LicenseRoundTrip license_messages(&s1);
|
|
InstallTestDrmKey(&s1);
|
|
ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest());
|
|
uint32_t prov_count1 =
|
|
license_messages.core_request().counter_info.provisioning_count;
|
|
uint32_t lic_count1 =
|
|
license_messages.core_request().counter_info.license_count;
|
|
uint32_t decrypt_count1 =
|
|
license_messages.core_request().counter_info.decrypt_count;
|
|
uint64_t master_generation_number1 =
|
|
license_messages.core_request().counter_info.master_generation_number;
|
|
|
|
// do the same as ReloadOfflineLicense to push the master generation number
|
|
// up
|
|
LicenseWithUsageEntry entry;
|
|
entry.license_messages().set_api_version(kCurrentAPI);
|
|
entry.MakeOfflineAndClose(this);
|
|
Session& s = entry.session();
|
|
ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this));
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused));
|
|
ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR());
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive));
|
|
|
|
Session s2;
|
|
s2.open();
|
|
LicenseRoundTrip license_messages2(&s2);
|
|
InstallTestDrmKey(&s2);
|
|
ASSERT_NO_FATAL_FAILURE(license_messages2.SignAndVerifyRequest());
|
|
uint32_t prov_count2 =
|
|
license_messages2.core_request().counter_info.provisioning_count;
|
|
uint32_t lic_count2 =
|
|
license_messages2.core_request().counter_info.license_count;
|
|
uint32_t decrypt_count2 =
|
|
license_messages2.core_request().counter_info.decrypt_count;
|
|
uint64_t master_generation_number2 =
|
|
license_messages2.core_request().counter_info.master_generation_number;
|
|
|
|
ASSERT_TRUE(prov_count2 == prov_count1);
|
|
ASSERT_TRUE(lic_count2 > lic_count1);
|
|
ASSERT_TRUE(master_generation_number2 > master_generation_number1);
|
|
|
|
// Log if decrypt counter hasn't gone up. Not a hard requirement, so don't
|
|
// assert for it.
|
|
if (decrypt_count2 <= decrypt_count1) {
|
|
LOGE("Decrypt count did not increase.");
|
|
}
|
|
}
|
|
|
|
TEST_P(OEMCryptoUsageTableTest,
|
|
OEMCryptoMemoryLoadUsageEntryForHugeInvalidUsageEntryNumber) {
|
|
LicenseWithUsageEntry entry;
|
|
entry.license_messages().set_api_version(license_api_version_);
|
|
// Make first entry 0.
|
|
entry.MakeOfflineAndClose(this);
|
|
Session s;
|
|
s.open();
|
|
InstallTestDrmKey(&s);
|
|
const uint32_t usage_entry_number = kHugeRandomNumber;
|
|
ASSERT_NO_FATAL_FAILURE(OEMCrypto_LoadUsageEntry(
|
|
s.session_id(), usage_entry_number, s.encrypted_usage_entry().data(),
|
|
s.encrypted_usage_entry().size()));
|
|
}
|
|
|
|
// Test an online or streaming license with PST. This license requires a
|
|
// valid nonce and can only be loaded once.
|
|
TEST_P(OEMCryptoUsageTableTest, OnlineLicense) {
|
|
LicenseWithUsageEntry entry;
|
|
entry.license_messages().set_api_version(license_api_version_);
|
|
entry.MakeAndLoadOnline(this);
|
|
Session& s = entry.session();
|
|
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
|
|
// test repeated report generation
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused));
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused));
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused));
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused));
|
|
ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR());
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive));
|
|
// Flag the entry as inactive.
|
|
ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry());
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
// It should report as inactive.
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed));
|
|
// Decrypt should fail.
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
entry.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE));
|
|
// We could call DeactivateUsageEntry multiple times. The state should not
|
|
// change.
|
|
ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry());
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
// It should report as inactive.
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed));
|
|
}
|
|
|
|
// Test the usage report when the license is loaded but the keys are never
|
|
// used for decryption.
|
|
TEST_P(OEMCryptoUsageTableTest, OnlineLicenseUnused) {
|
|
LicenseWithUsageEntry entry;
|
|
entry.license_messages().set_api_version(license_api_version_);
|
|
entry.MakeAndLoadOnline(this);
|
|
Session& s = entry.session();
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
// No decrypt. We do not use this license.
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused));
|
|
// Flag the entry as inactive.
|
|
ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry());
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
// It should report as inactive.
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUnused));
|
|
// Decrypt should fail.
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
entry.TestDecryptCTR(true, OEMCrypto_ERROR_UNKNOWN_FAILURE));
|
|
// We could call DeactivateUsageEntry multiple times. The state should not
|
|
// change.
|
|
ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry());
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
// It should report as inactive.
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUnused));
|
|
}
|
|
|
|
// Test that the usage table has been updated and saved before a report can be
|
|
// generated.
|
|
TEST_P(OEMCryptoUsageTableTest, ForbidReportWithNoUpdate) {
|
|
LicenseWithUsageEntry entry;
|
|
entry.license_messages().set_api_version(license_api_version_);
|
|
entry.MakeAndLoadOnline(this);
|
|
Session& s = entry.session();
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused));
|
|
ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR());
|
|
// Cannot generate a report without first updating the file.
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
s.GenerateReport(entry.pst(), OEMCrypto_ERROR_ENTRY_NEEDS_UPDATE));
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
// Now it's OK.
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive));
|
|
// Flag the entry as inactive.
|
|
ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry());
|
|
// Cannot generate a report without first updating the file.
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
s.GenerateReport(entry.pst(), OEMCrypto_ERROR_ENTRY_NEEDS_UPDATE));
|
|
// Decrypt should fail.
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
entry.TestDecryptCTR(true, OEMCrypto_ERROR_UNKNOWN_FAILURE));
|
|
}
|
|
|
|
// Test an online license with a license renewal.
|
|
TEST_P(OEMCryptoUsageTableTest, OnlineLicenseWithRefreshAPI16) {
|
|
LicenseWithUsageEntry entry;
|
|
entry.license_messages().set_api_version(license_api_version_);
|
|
entry.MakeAndLoadOnline(this);
|
|
Session& s = entry.session();
|
|
ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR());
|
|
|
|
RenewalRoundTrip renewal_messages(&entry.license_messages());
|
|
MakeRenewalRequest(&renewal_messages);
|
|
LoadRenewal(&renewal_messages, OEMCrypto_SUCCESS);
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive));
|
|
}
|
|
|
|
// Verify that a streaming license cannot be reloaded.
|
|
TEST_P(OEMCryptoUsageTableTest, RepeatOnlineLicense) {
|
|
LicenseWithUsageEntry entry;
|
|
entry.license_messages().set_api_version(license_api_version_);
|
|
entry.MakeAndLoadOnline(this);
|
|
Session& s = entry.session();
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(s.close());
|
|
Session s2;
|
|
ASSERT_NO_FATAL_FAILURE(s2.open());
|
|
ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s2));
|
|
s2.LoadUsageEntry(s); // Use the same entry.
|
|
ASSERT_NE(OEMCrypto_SUCCESS, entry.license_messages().LoadResponse(&s2));
|
|
}
|
|
|
|
// An offline license should not load on the first call if the nonce is bad.
|
|
TEST_P(OEMCryptoUsageTableTest, OnlineBadNonce) {
|
|
Session s;
|
|
LicenseRoundTrip license_messages(&s);
|
|
license_messages.set_api_version(license_api_version_);
|
|
license_messages.set_control(wvoec::kControlNonceEnabled |
|
|
wvoec::kControlNonceRequired);
|
|
license_messages.set_pst("my-pst");
|
|
ASSERT_NO_FATAL_FAILURE(s.open());
|
|
ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s));
|
|
ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry());
|
|
ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest());
|
|
ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse());
|
|
for (uint32_t i = 0; i < license_messages.num_keys(); i++)
|
|
license_messages.response_data().keys[i].control.nonce ^= 42;
|
|
ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse());
|
|
ASSERT_EQ(OEMCrypto_ERROR_INVALID_NONCE, license_messages.LoadResponse());
|
|
}
|
|
|
|
// A license with non-zero replay control bits needs a valid pst.
|
|
TEST_P(OEMCryptoUsageTableTest, OnlineEmptyPST) {
|
|
Session s;
|
|
ASSERT_NO_FATAL_FAILURE(s.open());
|
|
ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s));
|
|
LicenseRoundTrip license_messages(&s);
|
|
license_messages.set_api_version(license_api_version_);
|
|
license_messages.set_control(wvoec::kControlNonceEnabled |
|
|
wvoec::kControlNonceRequired);
|
|
// DO NOT SET PST: license_messages.set_pst(pst);
|
|
ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest());
|
|
ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse());
|
|
ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse());
|
|
ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry());
|
|
ASSERT_NE(OEMCrypto_SUCCESS, license_messages.LoadResponse());
|
|
}
|
|
|
|
// A license with non-zero replay control bits needs a valid pst.
|
|
TEST_P(OEMCryptoUsageTableTest, OnlineMissingEntry) {
|
|
Session s;
|
|
ASSERT_NO_FATAL_FAILURE(s.open());
|
|
ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s));
|
|
LicenseRoundTrip license_messages(&s);
|
|
license_messages.set_api_version(license_api_version_);
|
|
license_messages.set_control(wvoec::kControlNonceEnabled |
|
|
wvoec::kControlNonceRequired);
|
|
license_messages.set_pst("my-pst");
|
|
ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest());
|
|
ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse());
|
|
ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse());
|
|
// ENTRY NOT CREATED: ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry());
|
|
ASSERT_NE(OEMCrypto_SUCCESS, license_messages.LoadResponse());
|
|
}
|
|
|
|
// Sessions should have at most one entry at a time. This tests different
|
|
// orderings of CreateNewUsageEntry and LoadUsageEntry calls.
|
|
TEST_P(OEMCryptoUsageTableTest, CreateAndLoadMultipleEntriesAPI16) {
|
|
// Entry Count: we start each test with an empty header.
|
|
uint32_t usage_entry_number;
|
|
LicenseWithUsageEntry entry;
|
|
entry.license_messages().set_api_version(license_api_version_);
|
|
Session& s = entry.session();
|
|
// Make first entry 0.
|
|
ASSERT_NO_FATAL_FAILURE(entry.MakeOfflineAndClose(this));
|
|
|
|
// Load an entry, then try to create a second.
|
|
ASSERT_NO_FATAL_FAILURE(s.open());
|
|
// Reload entry 0.
|
|
ASSERT_NO_FATAL_FAILURE(s.ReloadUsageEntry());
|
|
// Create new entry 1 should fail.
|
|
ASSERT_NE(OEMCrypto_SUCCESS,
|
|
OEMCrypto_CreateNewUsageEntry(entry.session().session_id(),
|
|
&usage_entry_number));
|
|
ASSERT_NO_FATAL_FAILURE(s.close());
|
|
|
|
// Create an entry, then try to load a second.
|
|
Session s2;
|
|
ASSERT_NO_FATAL_FAILURE(s2.open());
|
|
// Create entry 1.
|
|
ASSERT_NO_FATAL_FAILURE(s2.CreateNewUsageEntry());
|
|
// Try to reload entry 0.
|
|
ASSERT_NE(OEMCrypto_SUCCESS,
|
|
OEMCrypto_LoadUsageEntry(s2.session_id(), s.usage_entry_number(),
|
|
s.encrypted_usage_entry().data(),
|
|
s.encrypted_usage_entry().size()));
|
|
ASSERT_NO_FATAL_FAILURE(s2.close());
|
|
|
|
// Reload an entry and a license, then try to load the same entry again.
|
|
// This reloads entry 0.
|
|
ASSERT_NO_FATAL_FAILURE(s.open());
|
|
ASSERT_NO_FATAL_FAILURE(s.ReloadUsageEntry());
|
|
ASSERT_NE(OEMCrypto_SUCCESS,
|
|
OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(),
|
|
s.encrypted_usage_entry().data(),
|
|
s.encrypted_usage_entry().size()));
|
|
ASSERT_NO_FATAL_FAILURE(s.close());
|
|
|
|
// Create an entry, then try to create a second entry.
|
|
ASSERT_NO_FATAL_FAILURE(s2.open());
|
|
ASSERT_NO_FATAL_FAILURE(s2.CreateNewUsageEntry());
|
|
ASSERT_NE(OEMCrypto_SUCCESS, OEMCrypto_CreateNewUsageEntry(
|
|
s2.session_id(), &usage_entry_number));
|
|
}
|
|
|
|
// An entry can be loaded in only one session at a time.
|
|
TEST_P(OEMCryptoUsageTableTest, LoadEntryInMultipleSessions) {
|
|
// Entry Count: we start each test with an empty header.
|
|
LicenseWithUsageEntry entry;
|
|
entry.license_messages().set_api_version(license_api_version_);
|
|
Session& s = entry.session();
|
|
// Make first entry 0.
|
|
ASSERT_NO_FATAL_FAILURE(entry.MakeOfflineAndClose(this));
|
|
const uint32_t usage_entry_number = s.usage_entry_number();
|
|
EXPECT_EQ(usage_entry_number, 0u); // Should be only entry in this test.
|
|
|
|
// Load an entry, then try to create a second.
|
|
ASSERT_NO_FATAL_FAILURE(s.open());
|
|
// Reload entry 0.
|
|
ASSERT_NO_FATAL_FAILURE(s.ReloadUsageEntry());
|
|
|
|
// Create an entry, then try to load a second.
|
|
Session s2;
|
|
ASSERT_NO_FATAL_FAILURE(s2.open());
|
|
// Try to load entry 0 into session 2.
|
|
ASSERT_EQ(OEMCrypto_ERROR_INVALID_SESSION,
|
|
OEMCrypto_LoadUsageEntry(s2.session_id(), usage_entry_number,
|
|
s.encrypted_usage_entry().data(),
|
|
s.encrypted_usage_entry().size()));
|
|
}
|
|
|
|
// Test generic encrypt when the license uses a PST.
|
|
TEST_P(OEMCryptoUsageTableTest, GenericCryptoEncrypt) {
|
|
if (!wvoec::global_features.generic_crypto) {
|
|
GTEST_SKIP() << "Test for devices with generic crypto API only";
|
|
}
|
|
LicenseWithUsageEntry entry;
|
|
entry.license_messages().set_api_version(license_api_version_);
|
|
entry.set_generic_crypto(true);
|
|
entry.MakeAndLoadOnline(this);
|
|
Session& s = entry.session();
|
|
OEMCryptoResult sts;
|
|
unsigned int key_index = 0;
|
|
vector<uint8_t> expected_encrypted;
|
|
EncryptBufferWithKey(s.license().keys[key_index].key_data, clear_buffer_,
|
|
&expected_encrypted);
|
|
vector<uint8_t> key_handle;
|
|
sts =
|
|
GetKeyHandleIntoVector(s.session_id(), s.license().keys[key_index].key_id,
|
|
s.license().keys[key_index].key_id_length,
|
|
OEMCrypto_CipherMode_CENC, key_handle);
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
|
vector<uint8_t> encrypted(clear_buffer_.size());
|
|
sts = OEMCrypto_Generic_Encrypt(key_handle.data(), key_handle.size(),
|
|
clear_buffer_.data(), clear_buffer_.size(),
|
|
iv_, OEMCrypto_AES_CBC_128_NO_PADDING,
|
|
encrypted.data());
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
|
EXPECT_EQ(expected_encrypted, encrypted);
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive));
|
|
ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry());
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed));
|
|
encrypted.assign(clear_buffer_.size(), 0);
|
|
sts = OEMCrypto_Generic_Encrypt(key_handle.data(), key_handle.size(),
|
|
clear_buffer_.data(), clear_buffer_.size(),
|
|
iv_, OEMCrypto_AES_CBC_128_NO_PADDING,
|
|
encrypted.data());
|
|
ASSERT_NE(OEMCrypto_SUCCESS, sts);
|
|
EXPECT_NE(encrypted, expected_encrypted);
|
|
}
|
|
|
|
// Test generic decrypt when the license uses a PST.
|
|
TEST_P(OEMCryptoUsageTableTest, GenericCryptoDecrypt) {
|
|
if (!wvoec::global_features.generic_crypto) {
|
|
GTEST_SKIP() << "Test for devices with generic crypto API only";
|
|
}
|
|
LicenseWithUsageEntry entry;
|
|
entry.license_messages().set_api_version(license_api_version_);
|
|
entry.set_generic_crypto(true);
|
|
entry.MakeAndLoadOnline(this);
|
|
Session& s = entry.session();
|
|
OEMCryptoResult sts;
|
|
unsigned int key_index = 1;
|
|
vector<uint8_t> encrypted;
|
|
EncryptBufferWithKey(s.license().keys[key_index].key_data, clear_buffer_,
|
|
&encrypted);
|
|
vector<uint8_t> key_handle;
|
|
sts =
|
|
GetKeyHandleIntoVector(s.session_id(), s.license().keys[key_index].key_id,
|
|
s.license().keys[key_index].key_id_length,
|
|
OEMCrypto_CipherMode_CENC, key_handle);
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
|
vector<uint8_t> resultant(encrypted.size());
|
|
sts = OEMCrypto_Generic_Decrypt(
|
|
key_handle.data(), key_handle.size(), encrypted.data(), encrypted.size(),
|
|
iv_, OEMCrypto_AES_CBC_128_NO_PADDING, resultant.data());
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
|
EXPECT_EQ(clear_buffer_, resultant);
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive));
|
|
ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry());
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed));
|
|
resultant.assign(encrypted.size(), 0);
|
|
sts = OEMCrypto_Generic_Decrypt(
|
|
key_handle.data(), key_handle.size(), encrypted.data(), encrypted.size(),
|
|
iv_, OEMCrypto_AES_CBC_128_NO_PADDING, resultant.data());
|
|
ASSERT_NE(OEMCrypto_SUCCESS, sts);
|
|
EXPECT_NE(clear_buffer_, resultant);
|
|
}
|
|
|
|
// Test generic sign when the license uses a PST.
|
|
TEST_P(OEMCryptoUsageTableTest, GenericCryptoSign) {
|
|
if (!wvoec::global_features.generic_crypto) {
|
|
GTEST_SKIP() << "Test for devices with generic crypto API only";
|
|
}
|
|
LicenseWithUsageEntry entry;
|
|
entry.license_messages().set_api_version(license_api_version_);
|
|
entry.set_generic_crypto(true);
|
|
entry.MakeAndLoadOnline(this);
|
|
Session& s = entry.session();
|
|
OEMCryptoResult sts;
|
|
unsigned int key_index = 2;
|
|
vector<uint8_t> expected_signature;
|
|
SignBufferWithKey(s.license().keys[key_index].key_data, clear_buffer_,
|
|
&expected_signature);
|
|
|
|
vector<uint8_t> key_handle;
|
|
sts =
|
|
GetKeyHandleIntoVector(s.session_id(), s.license().keys[key_index].key_id,
|
|
s.license().keys[key_index].key_id_length,
|
|
OEMCrypto_CipherMode_CENC, key_handle);
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
|
size_t gen_signature_length = 0;
|
|
sts = OEMCrypto_Generic_Sign(key_handle.data(), key_handle.size(),
|
|
clear_buffer_.data(), clear_buffer_.size(),
|
|
OEMCrypto_HMAC_SHA256, nullptr,
|
|
&gen_signature_length);
|
|
ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts);
|
|
ASSERT_EQ(static_cast<size_t>(SHA256_DIGEST_LENGTH), gen_signature_length);
|
|
vector<uint8_t> signature(SHA256_DIGEST_LENGTH);
|
|
sts = OEMCrypto_Generic_Sign(key_handle.data(), key_handle.size(),
|
|
clear_buffer_.data(), clear_buffer_.size(),
|
|
OEMCrypto_HMAC_SHA256, signature.data(),
|
|
&gen_signature_length);
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
|
ASSERT_EQ(expected_signature, signature);
|
|
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive));
|
|
ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry());
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed));
|
|
signature.assign(SHA256_DIGEST_LENGTH, 0);
|
|
gen_signature_length = SHA256_DIGEST_LENGTH;
|
|
sts = OEMCrypto_Generic_Sign(key_handle.data(), key_handle.size(),
|
|
clear_buffer_.data(), clear_buffer_.size(),
|
|
OEMCrypto_HMAC_SHA256, signature.data(),
|
|
&gen_signature_length);
|
|
ASSERT_NE(OEMCrypto_SUCCESS, sts);
|
|
ASSERT_NE(signature, expected_signature);
|
|
}
|
|
|
|
// Test generic verify when the license uses a PST.
|
|
TEST_P(OEMCryptoUsageTableTest, GenericCryptoVerify) {
|
|
if (!wvoec::global_features.generic_crypto) {
|
|
GTEST_SKIP() << "Test for devices with generic crypto API only";
|
|
}
|
|
LicenseWithUsageEntry entry;
|
|
entry.license_messages().set_api_version(license_api_version_);
|
|
entry.set_generic_crypto(true);
|
|
entry.MakeAndLoadOnline(this);
|
|
Session& s = entry.session();
|
|
OEMCryptoResult sts;
|
|
unsigned int key_index = 3;
|
|
vector<uint8_t> signature;
|
|
SignBufferWithKey(s.license().keys[key_index].key_data, clear_buffer_,
|
|
&signature);
|
|
|
|
vector<uint8_t> key_handle;
|
|
sts =
|
|
GetKeyHandleIntoVector(s.session_id(), s.license().keys[key_index].key_id,
|
|
s.license().keys[key_index].key_id_length,
|
|
OEMCrypto_CipherMode_CENC, key_handle);
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
|
sts = OEMCrypto_Generic_Verify(key_handle.data(), key_handle.size(),
|
|
clear_buffer_.data(), clear_buffer_.size(),
|
|
OEMCrypto_HMAC_SHA256, signature.data(),
|
|
signature.size());
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive));
|
|
ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry());
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed));
|
|
sts = OEMCrypto_Generic_Verify(key_handle.data(), key_handle.size(),
|
|
clear_buffer_.data(), clear_buffer_.size(),
|
|
OEMCrypto_HMAC_SHA256, signature.data(),
|
|
signature.size());
|
|
ASSERT_NE(OEMCrypto_SUCCESS, sts);
|
|
}
|
|
|
|
// Test that an offline license can be loaded.
|
|
TEST_P(OEMCryptoUsageTableTest, OfflineLicense) {
|
|
LicenseWithUsageEntry entry;
|
|
entry.license_messages().set_api_version(license_api_version_);
|
|
ASSERT_NO_FATAL_FAILURE(entry.MakeOfflineAndClose(this));
|
|
}
|
|
|
|
// Test that an offline license can be loaded and that the license can be
|
|
// renewed.
|
|
TEST_P(OEMCryptoUsageTableTest, OfflineLicenseRefresh) {
|
|
LicenseWithUsageEntry entry;
|
|
entry.license_messages().set_api_version(license_api_version_);
|
|
entry.MakeAndLoad(this, wvoec::kControlNonceOrEntry);
|
|
Session& s = entry.session();
|
|
|
|
ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR());
|
|
// License renewal message is signed by client and verified by the server.
|
|
RenewalRoundTrip renewal_messages(&entry.license_messages());
|
|
MakeRenewalRequest(&renewal_messages);
|
|
LoadRenewal(&renewal_messages, OEMCrypto_SUCCESS);
|
|
ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR());
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive));
|
|
}
|
|
|
|
// Test that an offline license can be loaded and that the license can be
|
|
// released
|
|
TEST_P(OEMCryptoUsageTableTest, OfflineLicenseReleaseAPI19) {
|
|
// License release is new in OEMCrypto v19.
|
|
if (wvoec::global_features.api_version < 19 || license_api_version_ < 19) {
|
|
GTEST_SKIP() << "Test for versions 19 and up only.";
|
|
}
|
|
LicenseWithUsageEntry entry;
|
|
entry.license_messages().set_api_version(license_api_version_);
|
|
entry.MakeAndLoad(this, wvoec::kControlNonceOrEntry);
|
|
Session& s = entry.session();
|
|
|
|
ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR());
|
|
// License release message is signed by client and verified by the server.
|
|
ReleaseRoundTrip release_messages(&entry.license_messages());
|
|
MakeReleaseRequest(&release_messages);
|
|
LoadRelease(&release_messages, OEMCrypto_SUCCESS);
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
entry.TestDecryptCTR(true, OEMCrypto_ERROR_UNKNOWN_FAILURE));
|
|
}
|
|
|
|
// Test that an offline license can be reloaded in a new session.
|
|
TEST_P(OEMCryptoUsageTableTest, ReloadOfflineLicense) {
|
|
LicenseWithUsageEntry entry;
|
|
entry.license_messages().set_api_version(license_api_version_);
|
|
entry.MakeOfflineAndClose(this);
|
|
Session& s = entry.session();
|
|
ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this));
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused));
|
|
ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR());
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive));
|
|
}
|
|
|
|
// Test that an offline license can be reloaded in a new session, and then
|
|
// refreshed.
|
|
TEST_P(OEMCryptoUsageTableTest, ReloadOfflineLicenseWithRefresh) {
|
|
LicenseWithUsageEntry entry;
|
|
entry.license_messages().set_api_version(license_api_version_);
|
|
entry.MakeOfflineAndClose(this);
|
|
Session& s = entry.session();
|
|
|
|
ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this));
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused));
|
|
ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR());
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
RenewalRoundTrip renewal_messages(&entry.license_messages());
|
|
MakeRenewalRequest(&renewal_messages);
|
|
LoadRenewal(&renewal_messages, OEMCrypto_SUCCESS);
|
|
ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR());
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive));
|
|
}
|
|
|
|
// Verify that we can still reload an offline license after
|
|
// OEMCrypto_Terminate and Initialize are called. This is as close to a reboot
|
|
// as we can do in a unit test.
|
|
TEST_P(OEMCryptoUsageTableTest, ReloadOfflineLicenseWithTerminate) {
|
|
LicenseWithUsageEntry entry;
|
|
entry.license_messages().set_api_version(license_api_version_);
|
|
entry.MakeOfflineAndClose(this);
|
|
Session& s = entry.session();
|
|
ShutDown(); // This calls OEMCrypto_Terminate.
|
|
Restart(); // This calls OEMCrypto_Initialize.
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, LoadUsageTableHeader(encrypted_usage_header_));
|
|
|
|
ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this));
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused));
|
|
ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR());
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive));
|
|
}
|
|
|
|
// If we attempt to load a second license with the same usage entry as the
|
|
// first, but it has different mac keys, then the attempt should fail. This
|
|
// is how we verify that we are reloading the same license.
|
|
TEST_P(OEMCryptoUsageTableTest, BadReloadOfflineLicense) {
|
|
LicenseWithUsageEntry entry;
|
|
entry.license_messages().set_api_version(license_api_version_);
|
|
entry.MakeOfflineAndClose(this);
|
|
Session& s = entry.session();
|
|
|
|
// Offline license with new mac keys should fail.
|
|
Session s2;
|
|
LicenseRoundTrip license_messages2(&s2);
|
|
// Copy the response, and then change the mac keys.
|
|
license_messages2.response_data() = entry.license_messages().response_data();
|
|
license_messages2.core_response() = entry.license_messages().core_response();
|
|
license_messages2.response_data().mac_keys[7] ^= 42;
|
|
ASSERT_NO_FATAL_FAILURE(s2.open());
|
|
ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s2));
|
|
// This is a valid license: it is correctly signed.
|
|
license_messages2.EncryptAndSignResponse();
|
|
// Load the usage entry should be OK.
|
|
ASSERT_NO_FATAL_FAILURE(s2.LoadUsageEntry(s));
|
|
ASSERT_NE(OEMCrypto_SUCCESS, license_messages2.LoadResponse());
|
|
ASSERT_NO_FATAL_FAILURE(s2.close());
|
|
|
|
// Now we go back to the original license response. It should load OK.
|
|
ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this));
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused));
|
|
}
|
|
|
|
// An offline license should not load on the first call if the nonce is bad.
|
|
TEST_P(OEMCryptoUsageTableTest, OfflineBadNonce) {
|
|
Session s;
|
|
LicenseRoundTrip license_messages(&s);
|
|
license_messages.set_api_version(license_api_version_);
|
|
license_messages.set_control(wvoec::kControlNonceOrEntry);
|
|
license_messages.set_pst("my-pst");
|
|
ASSERT_NO_FATAL_FAILURE(s.open());
|
|
ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s));
|
|
ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry());
|
|
ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest());
|
|
ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse());
|
|
for (size_t i = 0; i < license_messages.num_keys(); i++)
|
|
license_messages.response_data().keys[i].control.nonce ^= 42;
|
|
ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse());
|
|
ASSERT_EQ(OEMCrypto_ERROR_INVALID_NONCE, license_messages.LoadResponse());
|
|
}
|
|
|
|
// An offline license needs a valid pst.
|
|
TEST_P(OEMCryptoUsageTableTest, OfflineEmptyPST) {
|
|
Session s;
|
|
ASSERT_NO_FATAL_FAILURE(s.open());
|
|
ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s));
|
|
LicenseRoundTrip license_messages(&s);
|
|
license_messages.set_api_version(license_api_version_);
|
|
license_messages.set_control(wvoec::kControlNonceOrEntry);
|
|
// DO NOT SET PST: license_messages.set_pst(pst);
|
|
ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest());
|
|
ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse());
|
|
ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse());
|
|
ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry());
|
|
ASSERT_NE(OEMCrypto_SUCCESS, license_messages.LoadResponse());
|
|
}
|
|
|
|
// If we try to reload a license with a different PST, the attempt should
|
|
// fail.
|
|
TEST_P(OEMCryptoUsageTableTest, ReloadOfflineWrongPST) {
|
|
LicenseWithUsageEntry entry;
|
|
entry.license_messages().set_api_version(license_api_version_);
|
|
entry.MakeOfflineAndClose(this);
|
|
Session& s = entry.session();
|
|
|
|
Session s2;
|
|
LicenseRoundTrip license_messages2(&s2);
|
|
license_messages2.response_data() = entry.license_messages().response_data();
|
|
license_messages2.core_response() = entry.license_messages().core_response();
|
|
// Change the middle of the pst.
|
|
license_messages2.response_data().pst[3] ^= 'Z';
|
|
ASSERT_NO_FATAL_FAILURE(s2.open());
|
|
ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s2));
|
|
// This is a valid license: it is correctly signed.
|
|
license_messages2.EncryptAndSignResponse();
|
|
// Load the usage entry should be OK.
|
|
ASSERT_NO_FATAL_FAILURE(s2.LoadUsageEntry(s));
|
|
ASSERT_NE(OEMCrypto_SUCCESS, license_messages2.LoadResponse());
|
|
}
|
|
|
|
// Once a license has been deactivated, the keys can no longer be used for
|
|
// decryption. However, we can still generate a usage report.
|
|
TEST_P(OEMCryptoUsageTableTest, DeactivateOfflineLicense) {
|
|
LicenseWithUsageEntry entry;
|
|
entry.license_messages().set_api_version(license_api_version_);
|
|
entry.MakeOfflineAndClose(this);
|
|
Session& s = entry.session();
|
|
// Reload the offline license.
|
|
ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this));
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
entry.TestDecryptCTR()); // Should be able to decrypt.
|
|
ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); // Then deactivate.
|
|
// After deactivate, should not be able to decrypt.
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
entry.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE));
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed));
|
|
ASSERT_NO_FATAL_FAILURE(s.close());
|
|
|
|
// Offline license can not be reused if it has been deactivated.
|
|
ASSERT_NO_FATAL_FAILURE(s.open());
|
|
ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s));
|
|
ASSERT_NE(OEMCrypto_SUCCESS, entry.license_messages().LoadResponse(&s));
|
|
s.close();
|
|
|
|
// But we can still generate a report.
|
|
ASSERT_NO_FATAL_FAILURE(s.open());
|
|
ASSERT_NO_FATAL_FAILURE(entry.ReloadUsageEntry());
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
// Sending a release from an offline license that has been deactivate will
|
|
// only work if the license server can handle v16 licenses. This is a rare
|
|
// condition, so it is OK to break it during the transition months.
|
|
entry.license_messages().set_api_version(global_features.api_version);
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed));
|
|
// We could call DeactivateUsageEntry multiple times. The state should not
|
|
// change.
|
|
ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry());
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed));
|
|
}
|
|
|
|
// The usage report should indicate that the keys were never used for
|
|
// decryption.
|
|
TEST_P(OEMCryptoUsageTableTest, DeactivateOfflineLicenseUnused) {
|
|
LicenseWithUsageEntry entry;
|
|
entry.license_messages().set_api_version(license_api_version_);
|
|
entry.MakeOfflineAndClose(this);
|
|
Session& s = entry.session();
|
|
// No Decrypt. This license is unused.
|
|
ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this));
|
|
ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); // Then deactivate.
|
|
// After deactivate, should not be able to decrypt.
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
entry.TestDecryptCTR(true, OEMCrypto_ERROR_UNKNOWN_FAILURE));
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUnused));
|
|
ASSERT_NO_FATAL_FAILURE(s.close());
|
|
|
|
// Offline license can not be reused if it has been deactivated.
|
|
ASSERT_NO_FATAL_FAILURE(s.open());
|
|
ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s));
|
|
ASSERT_NE(OEMCrypto_SUCCESS, entry.license_messages().LoadResponse(&s));
|
|
s.close();
|
|
|
|
// But we can still generate a report.
|
|
ASSERT_NO_FATAL_FAILURE(s.open());
|
|
ASSERT_NO_FATAL_FAILURE(entry.ReloadUsageEntry());
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
// Sending a release from an offline license that has been deactivate will
|
|
// only work if the license server can handle v16 licenses. This is a rare
|
|
// condition, so it is OK to break it during the transition months.
|
|
entry.license_messages().set_api_version(global_features.api_version);
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUnused));
|
|
// We could call DeactivateUsageEntry multiple times. The state should not
|
|
// change.
|
|
ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry());
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUnused));
|
|
}
|
|
|
|
TEST_P(OEMCryptoUsageTableTest, SecureStop) {
|
|
LicenseWithUsageEntry entry;
|
|
entry.license_messages().set_api_version(license_api_version_);
|
|
entry.MakeAndLoadOnline(this);
|
|
Session& s = entry.session();
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR());
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(s.close());
|
|
// When we generate a secure stop without loading the license first, it
|
|
// should assume the server does not support core messages.
|
|
ASSERT_NO_FATAL_FAILURE(s.open());
|
|
ASSERT_NO_FATAL_FAILURE(entry.ReloadUsageEntry());
|
|
ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry());
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
// It should report as inactive.
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed));
|
|
}
|
|
|
|
// Test update usage table fails when passed a null pointer.
|
|
TEST_P(OEMCryptoUsageTableTest, UpdateFailsWithNullPtr) {
|
|
LicenseWithUsageEntry entry;
|
|
entry.license_messages().set_api_version(license_api_version_);
|
|
entry.MakeAndLoadOnline(this);
|
|
Session& s = entry.session();
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
size_t header_buffer_length = encrypted_usage_header_.size();
|
|
size_t entry_buffer_length = s.encrypted_usage_entry().size();
|
|
vector<uint8_t> buffer(entry_buffer_length);
|
|
// Now try to pass in null pointers for the buffers. This should fail.
|
|
ASSERT_NE(
|
|
OEMCrypto_SUCCESS,
|
|
OEMCrypto_UpdateUsageEntry(s.session_id(), nullptr, &header_buffer_length,
|
|
buffer.data(), &entry_buffer_length));
|
|
ASSERT_NE(OEMCrypto_SUCCESS,
|
|
OEMCrypto_UpdateUsageEntry(
|
|
s.session_id(), encrypted_usage_header_.data(),
|
|
&header_buffer_length, nullptr, &entry_buffer_length));
|
|
}
|
|
|
|
// Class used to test usage table defragmentation.
|
|
class OEMCryptoUsageTableDefragTest : public OEMCryptoUsageTableTest {
|
|
protected:
|
|
void ReloadLicense(LicenseWithUsageEntry* entry) {
|
|
Session& s = entry->session();
|
|
ASSERT_NO_FATAL_FAILURE(entry->OpenAndReload(this));
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(entry->TestDecryptCTR());
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(entry->GenerateVerifyReport(kActive));
|
|
ASSERT_NO_FATAL_FAILURE(s.close());
|
|
}
|
|
|
|
void FailReloadLicense(LicenseWithUsageEntry* entry,
|
|
OEMCryptoResult expected_result) {
|
|
Session& s = entry->session();
|
|
ASSERT_NO_FATAL_FAILURE(s.open());
|
|
ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s));
|
|
ASSERT_EQ(expected_result,
|
|
OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(),
|
|
s.encrypted_usage_entry().data(),
|
|
s.encrypted_usage_entry().size()));
|
|
|
|
ASSERT_NE(OEMCrypto_SUCCESS, entry->license_messages().LoadResponse());
|
|
ASSERT_NO_FATAL_FAILURE(s.close());
|
|
}
|
|
|
|
void ShrinkHeader(uint32_t new_size,
|
|
OEMCryptoResult expected_result = OEMCrypto_SUCCESS) {
|
|
// We call OEMCrypto_ShrinkUsageTableHeader once with a zero length
|
|
// buffer, so that OEMCrypto can tell us how big the buffer should be.
|
|
size_t header_buffer_length = 0;
|
|
OEMCryptoResult sts = OEMCrypto_ShrinkUsageTableHeader(
|
|
new_size, nullptr, &header_buffer_length);
|
|
// If we are expecting success, then the first call shall return
|
|
// SHORT_BUFFER. However, if we are not expecting success, this first call
|
|
// may return either SHORT_BUFFER or the expect error.
|
|
if (expected_result == OEMCrypto_SUCCESS) {
|
|
ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts);
|
|
} else if (sts != OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
// If we got any thing from the first call, it should be the expected
|
|
// error, and we don't need to call a second time.
|
|
ASSERT_EQ(expected_result, sts);
|
|
return;
|
|
}
|
|
// If the first call resulted in SHORT_BUFFER, we should resize the buffer
|
|
// and try again.
|
|
ASSERT_LT(0u, header_buffer_length);
|
|
encrypted_usage_header_.resize(header_buffer_length);
|
|
sts = OEMCrypto_ShrinkUsageTableHeader(
|
|
new_size, encrypted_usage_header_.data(), &header_buffer_length);
|
|
// For the second call, we always demand the expected result.
|
|
ASSERT_EQ(expected_result, sts);
|
|
if (sts == OEMCrypto_SUCCESS) {
|
|
encrypted_usage_header_.resize(header_buffer_length);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Verify that usage table entries can be moved around in the table.
|
|
TEST_P(OEMCryptoUsageTableDefragTest, MoveUsageEntries) {
|
|
const size_t ENTRY_COUNT = 10;
|
|
vector<LicenseWithUsageEntry> entries(ENTRY_COUNT);
|
|
for (size_t i = 0; i < ENTRY_COUNT; i++) {
|
|
entries[i].set_pst("pst " + std::to_string(i));
|
|
ASSERT_NO_FATAL_FAILURE(entries[i].MakeOfflineAndClose(this))
|
|
<< "On license " << i << " pst=" << entries[i].pst();
|
|
wvutil::TestSleep::SyncFakeClock();
|
|
}
|
|
for (size_t i = 0; i < ENTRY_COUNT; i++) {
|
|
ASSERT_NO_FATAL_FAILURE(entries[i].OpenAndReload(this))
|
|
<< "On license " << i << " pst=" << entries[i].pst();
|
|
ASSERT_NO_FATAL_FAILURE(entries[i].session().close())
|
|
<< "On license " << i << " pst=" << entries[i].pst();
|
|
}
|
|
// Move 4 to 1.
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
entries[4].session().MoveUsageEntry(1, &encrypted_usage_header_));
|
|
// Shrink header to 3 entries 0, 1 was 4, 2.
|
|
ASSERT_NO_FATAL_FAILURE(ShrinkHeader(3));
|
|
ShutDown();
|
|
Restart();
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, LoadUsageTableHeader(encrypted_usage_header_));
|
|
wvutil::TestSleep::SyncFakeClock();
|
|
ASSERT_NO_FATAL_FAILURE(ReloadLicense(&entries[0]));
|
|
// Now has index 1.
|
|
ASSERT_NO_FATAL_FAILURE(ReloadLicense(&entries[4]));
|
|
ASSERT_NO_FATAL_FAILURE(ReloadLicense(&entries[2]));
|
|
// When 4 was moved to 1, it increased the gen. number in the header.
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
FailReloadLicense(&entries[1], OEMCrypto_ERROR_GENERATION_SKEW));
|
|
// Index 3 is beyond the end of the table.
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
FailReloadLicense(&entries[3], OEMCrypto_ERROR_UNKNOWN_FAILURE));
|
|
}
|
|
|
|
TEST_P(OEMCryptoUsageTableDefragTest, MakeAndMoveEntry) {
|
|
// 1. Make an entry then close.
|
|
LicenseWithUsageEntry entry;
|
|
ASSERT_NO_FATAL_FAILURE(entry.set_pst("pst 0"));
|
|
ASSERT_NO_FATAL_FAILURE(entry.MakeOfflineAndClose(this));
|
|
ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this));
|
|
ASSERT_NO_FATAL_FAILURE(entry.session().close());
|
|
|
|
// 2. Make an entry then immediately move it into the previous slot.
|
|
// Not using helper functions because they shoehorn the session state into
|
|
// a limited set of possibilities. We want to create the specific case of
|
|
// immediately moving a newly created entry.
|
|
|
|
// Like LicenseWithUsageEntry::MakeAndLoad() but stop after creating the new
|
|
// usage entry.
|
|
Session session;
|
|
ASSERT_NO_FATAL_FAILURE(session.open());
|
|
ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&session));
|
|
LicenseRoundTrip license_messages_(&session);
|
|
license_messages_.set_control(wvoec::kControlNonceOrEntry);
|
|
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
|
|
ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse());
|
|
ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse());
|
|
OEMCryptoResult result;
|
|
ASSERT_NO_FATAL_FAILURE(session.CreateNewUsageEntry(&result));
|
|
|
|
// Not the same as Session::MoveUsageEntry, which opens and closes a session
|
|
// around the move operation. We just want to call MoveEntry on the current
|
|
// state.
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_MoveEntry(session.session_id(), 0));
|
|
ASSERT_NO_FATAL_FAILURE(session.close());
|
|
}
|
|
|
|
// A usage table entry cannot be moved into an entry where an open session is
|
|
// currently using the entry.
|
|
TEST_P(OEMCryptoUsageTableDefragTest, MoveUsageEntriesToOpenSession) {
|
|
LicenseWithUsageEntry entry0;
|
|
entry0.set_pst("pst 0");
|
|
entry0.MakeOfflineAndClose(this);
|
|
LicenseWithUsageEntry entry1;
|
|
entry1.set_pst("pst 1");
|
|
entry1.MakeOfflineAndClose(this);
|
|
|
|
entry0.session().open();
|
|
ASSERT_NO_FATAL_FAILURE(entry0.ReloadUsageEntry());
|
|
// s0 currently open on index 0. Expect this to fail:
|
|
ASSERT_NO_FATAL_FAILURE(entry1.session().MoveUsageEntry(
|
|
0, &encrypted_usage_header_, OEMCrypto_ERROR_ENTRY_IN_USE));
|
|
}
|
|
|
|
TEST_P(OEMCryptoUsageTableDefragTest, MoveUsageEntriesToInvalidHugeEntryIndex) {
|
|
LicenseWithUsageEntry entry0;
|
|
entry0.set_pst("pst 0");
|
|
entry0.MakeOfflineAndClose(this);
|
|
entry0.session().open();
|
|
entry0.ReloadUsageEntry();
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
OEMCrypto_MoveEntry(entry0.session().session_id(), kHugeRandomNumber));
|
|
}
|
|
|
|
// The usage table cannot be shrunk if any session is using an entry that
|
|
// would be deleted.
|
|
TEST_P(OEMCryptoUsageTableDefragTest, ShrinkOverOpenSessions) {
|
|
LicenseWithUsageEntry entry0;
|
|
entry0.set_pst("pst 0");
|
|
entry0.MakeOfflineAndClose(this);
|
|
LicenseWithUsageEntry entry1;
|
|
entry1.set_pst("pst 1");
|
|
entry1.MakeOfflineAndClose(this);
|
|
|
|
entry0.session().open();
|
|
ASSERT_NO_FATAL_FAILURE(entry0.ReloadUsageEntry());
|
|
entry1.session().open();
|
|
ASSERT_NO_FATAL_FAILURE(entry1.ReloadUsageEntry());
|
|
// Since s0 and s1 are open, we can't shrink.
|
|
ASSERT_NO_FATAL_FAILURE(ShrinkHeader(1, OEMCrypto_ERROR_ENTRY_IN_USE));
|
|
entry1.session().close(); // Can shrink after closing s1, even if s0 is open.
|
|
ASSERT_NO_FATAL_FAILURE(ShrinkHeader(1, OEMCrypto_SUCCESS));
|
|
}
|
|
|
|
// Verify the usage table size can be increased.
|
|
TEST_P(OEMCryptoUsageTableDefragTest, EnlargeHeader) {
|
|
LicenseWithUsageEntry entry0;
|
|
entry0.set_pst("pst 0");
|
|
entry0.MakeOfflineAndClose(this);
|
|
LicenseWithUsageEntry entry1;
|
|
entry1.set_pst("pst 1");
|
|
entry1.MakeOfflineAndClose(this);
|
|
|
|
// Can only shrink the header -- not make it bigger.
|
|
ASSERT_NO_FATAL_FAILURE(ShrinkHeader(4, OEMCrypto_ERROR_UNKNOWN_FAILURE));
|
|
}
|
|
|
|
// A new header can only be created while no entries are in use.
|
|
TEST_P(OEMCryptoUsageTableDefragTest, CreateNewHeaderWhileUsingOldOne) {
|
|
LicenseWithUsageEntry entry0;
|
|
entry0.set_pst("pst 0");
|
|
entry0.MakeOfflineAndClose(this);
|
|
LicenseWithUsageEntry entry1;
|
|
entry1.set_pst("pst 1");
|
|
entry1.MakeOfflineAndClose(this);
|
|
|
|
entry0.session().open();
|
|
ASSERT_NO_FATAL_FAILURE(entry0.ReloadUsageEntry());
|
|
const bool kExpectFailure = false;
|
|
ASSERT_NO_FATAL_FAILURE(CreateUsageTableHeader(kExpectFailure));
|
|
}
|
|
|
|
// Verify that a usage table entry can only be loaded into the correct index
|
|
// of the table.
|
|
TEST_P(OEMCryptoUsageTableDefragTest, ReloadUsageEntryWrongIndex) {
|
|
LicenseWithUsageEntry entry0;
|
|
entry0.set_pst("pst 0");
|
|
entry0.MakeOfflineAndClose(this);
|
|
LicenseWithUsageEntry entry1;
|
|
entry1.set_pst("pst 1");
|
|
entry1.MakeOfflineAndClose(this);
|
|
|
|
entry0.session().set_usage_entry_number(1);
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
FailReloadLicense(&entry0, OEMCrypto_ERROR_INVALID_SESSION));
|
|
}
|
|
|
|
// Verify that a usage table entry cannot be loaded if it has been altered.
|
|
TEST_P(OEMCryptoUsageTableDefragTest, ReloadUsageEntryBadData) {
|
|
LicenseWithUsageEntry entry;
|
|
entry.MakeOfflineAndClose(this);
|
|
Session& s = entry.session();
|
|
ASSERT_NO_FATAL_FAILURE(s.open());
|
|
ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s));
|
|
vector<uint8_t> data = s.encrypted_usage_entry();
|
|
ASSERT_LT(0UL, data.size());
|
|
data[0] ^= 42;
|
|
// Error could be signature or verification error.
|
|
ASSERT_NE(OEMCrypto_SUCCESS,
|
|
OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(),
|
|
data.data(), data.size()));
|
|
}
|
|
|
|
// This verifies we can actually create the required number of usage table
|
|
// entries.
|
|
TEST_P(OEMCryptoUsageTableDefragTest, ManyUsageEntries) {
|
|
// OEMCrypto is required to store at least 300 entries in the usage table
|
|
// header, but it is allowed to store more. This test verifies that if we
|
|
// keep adding entries, the error indicates a resource limit. It then
|
|
// verifies that all of the successful entries are still valid after we
|
|
// throw out the last invalid entry.
|
|
|
|
// After API 16, we require 300 entries in the usage table. Before API 16,
|
|
// we required 200.
|
|
const size_t required_capacity = RequiredUsageSize();
|
|
|
|
// We try to make a much large header, and assume there is an error at some
|
|
// point.
|
|
const size_t attempt_count = required_capacity * 5;
|
|
// Count of how many entries we successfully create.
|
|
size_t successful_count = 0;
|
|
|
|
// These entries have licenses tied to them.
|
|
std::vector<std::unique_ptr<LicenseWithUsageEntry>> entries;
|
|
// Store the status of the last attempt to create an entry.
|
|
OEMCryptoResult status = OEMCrypto_SUCCESS;
|
|
while (successful_count < attempt_count && status == OEMCrypto_SUCCESS) {
|
|
wvutil::TestSleep::SyncFakeClock();
|
|
LOGD("Creating license for entry %zu", successful_count);
|
|
entries.push_back(
|
|
std::unique_ptr<LicenseWithUsageEntry>(new LicenseWithUsageEntry()));
|
|
entries.back()->set_pst("pst " + std::to_string(successful_count));
|
|
ASSERT_NO_FATAL_FAILURE(entries.back()->MakeOfflineAndClose(this, &status))
|
|
<< "Failed creating license for entry " << successful_count;
|
|
if (status != OEMCrypto_SUCCESS) {
|
|
// Remove the failed session.
|
|
entries.resize(entries.size() - 1);
|
|
break;
|
|
}
|
|
EXPECT_EQ(entries.back()->session().usage_entry_number(), successful_count);
|
|
successful_count++;
|
|
// We don't create a license for each entry. For every license, we'll
|
|
// create 10 empty entries.
|
|
constexpr size_t filler_count = 10;
|
|
for (size_t i = 0; i < filler_count; i++) {
|
|
Session s;
|
|
ASSERT_NO_FATAL_FAILURE(s.open());
|
|
ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry(&status))
|
|
<< "Failed creating entry " << successful_count;
|
|
if (status != OEMCrypto_SUCCESS) break;
|
|
EXPECT_EQ(s.usage_entry_number(), successful_count);
|
|
successful_count++;
|
|
}
|
|
}
|
|
LOGD("successful_count = %zu", successful_count);
|
|
if (status != OEMCrypto_SUCCESS) {
|
|
// If we failed to create this many entries because of limited resources,
|
|
// then the error returned should be insufficient resources.
|
|
EXPECT_EQ(OEMCrypto_ERROR_INSUFFICIENT_RESOURCES, status)
|
|
<< "Failed to create license " << successful_count
|
|
<< ", with wrong error code.";
|
|
}
|
|
EXPECT_GE(successful_count, required_capacity);
|
|
wvutil::TestSleep::SyncFakeClock();
|
|
// Shrink the table a little.
|
|
constexpr size_t small_number = 5;
|
|
size_t smaller_size = successful_count - small_number;
|
|
ASSERT_NO_FATAL_FAILURE(ShrinkHeader(static_cast<uint32_t>(smaller_size)));
|
|
// Throw out the last license if it was in the part of the table that was
|
|
// shrunk.
|
|
if (entries.back()->session().usage_entry_number() >= smaller_size) {
|
|
entries.pop_back();
|
|
}
|
|
// Create a few more license
|
|
for (size_t i = 0; i < small_number; i++) {
|
|
wvutil::TestSleep::SyncFakeClock();
|
|
entries.push_back(
|
|
std::unique_ptr<LicenseWithUsageEntry>(new LicenseWithUsageEntry()));
|
|
entries.back()->set_pst("new pst " + std::to_string(smaller_size + i));
|
|
entries.back()->MakeOfflineAndClose(this);
|
|
}
|
|
// Make sure that all of the licenses can be reloaded.
|
|
for (size_t i = 0; i < entries.size(); i++) {
|
|
wvutil::TestSleep::SyncFakeClock();
|
|
Session& s = entries[i]->session();
|
|
ASSERT_NO_FATAL_FAILURE(entries[i]->OpenAndReload(this));
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(entries[i]->GenerateVerifyReport(kUnused));
|
|
ASSERT_NO_FATAL_FAILURE(entries[i]->TestDecryptCTR());
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(entries[i]->GenerateVerifyReport(kActive));
|
|
ASSERT_NO_FATAL_FAILURE(s.close());
|
|
}
|
|
}
|
|
|
|
// Verify that usage entries can be created in the position of existing entry
|
|
// indexes.
|
|
TEST_P(OEMCryptoUsageTableDefragTest, ReuseUsageEntryAPI17) {
|
|
if (wvoec::global_features.api_version < 17) {
|
|
GTEST_SKIP() << "Test for versions 17 and up only.";
|
|
}
|
|
LicenseWithUsageEntry entry0;
|
|
entry0.set_pst("pst 0");
|
|
LicenseWithUsageEntry entry1;
|
|
entry1.set_pst("pst 1");
|
|
|
|
entry0.session().open();
|
|
ASSERT_NO_FATAL_FAILURE(entry0.session().CreateNewUsageEntry());
|
|
const uint32_t number = entry0.session().usage_entry_number();
|
|
entry0.session().close();
|
|
entry1.session().open();
|
|
ASSERT_EQ(OEMCrypto_SUCCESS,
|
|
OEMCrypto_ReuseUsageEntry(entry1.session().session_id(), number));
|
|
}
|
|
|
|
// Verify that usage entries cannot replace an entry that is currently in
|
|
// use by a session.
|
|
TEST_P(OEMCryptoUsageTableDefragTest, ReuseUsageEntryIndexInUseAPI17) {
|
|
if (wvoec::global_features.api_version < 17) {
|
|
GTEST_SKIP() << "Test for versions 17 and up only.";
|
|
}
|
|
LicenseWithUsageEntry entry0;
|
|
entry0.set_pst("pst 0");
|
|
LicenseWithUsageEntry entry1;
|
|
entry1.set_pst("pst 1");
|
|
|
|
entry0.session().open();
|
|
ASSERT_NO_FATAL_FAILURE(entry0.session().CreateNewUsageEntry());
|
|
const uint32_t number = entry0.session().usage_entry_number();
|
|
entry1.session().open();
|
|
ASSERT_EQ(OEMCrypto_ERROR_INVALID_SESSION,
|
|
OEMCrypto_ReuseUsageEntry(entry1.session().session_id(), number));
|
|
}
|
|
|
|
// Verify that usage entries cannot be created if the usage entry index is
|
|
// too large.
|
|
TEST_P(OEMCryptoUsageTableDefragTest, ReuseUsageEntryWithInvalidIndexAPI17) {
|
|
if (wvoec::global_features.api_version < 17) {
|
|
GTEST_SKIP() << "Test for versions 17 and up only.";
|
|
}
|
|
LicenseWithUsageEntry entry0;
|
|
entry0.set_pst("pst 0");
|
|
LicenseWithUsageEntry entry1;
|
|
entry1.set_pst("pst 1");
|
|
|
|
entry0.session().open();
|
|
ASSERT_NO_FATAL_FAILURE(entry0.session().CreateNewUsageEntry());
|
|
const uint32_t number = entry0.session().usage_entry_number();
|
|
entry0.session().close();
|
|
entry1.session().open();
|
|
ASSERT_EQ(
|
|
OEMCrypto_ERROR_UNKNOWN_FAILURE,
|
|
OEMCrypto_ReuseUsageEntry(entry1.session().session_id(), number + 42));
|
|
}
|
|
|
|
// Verify that usage entries cannot be created if the session already has an
|
|
// entry.
|
|
TEST_P(OEMCryptoUsageTableDefragTest,
|
|
ReuseUsageEntrySessionAlreadyHasEntryAPI17) {
|
|
if (wvoec::global_features.api_version < 17) {
|
|
GTEST_SKIP() << "Test for versions 17 and up only.";
|
|
}
|
|
LicenseWithUsageEntry entry;
|
|
entry.set_pst("pst 0");
|
|
|
|
// Create 5 entries in the table.
|
|
for (int i = 0; i < 5; i++) {
|
|
entry.session().open();
|
|
ASSERT_NO_FATAL_FAILURE(entry.session().CreateNewUsageEntry());
|
|
entry.session().close();
|
|
}
|
|
entry.session().open();
|
|
ASSERT_NO_FATAL_FAILURE(entry.session().CreateNewUsageEntry());
|
|
const uint32_t number = entry.session().usage_entry_number();
|
|
ASSERT_EQ(
|
|
OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES,
|
|
OEMCrypto_ReuseUsageEntry(entry.session().session_id(), number - 3));
|
|
}
|
|
|
|
// This verifies that the usage table header can be loaded if the generation
|
|
// number is off by one, but not off by two.
|
|
TEST_P(OEMCryptoUsageTableTest, ReloadUsageTableWithSkew) {
|
|
// This also tests a few other error conditions with usage table headers.
|
|
LicenseWithUsageEntry entry;
|
|
entry.MakeOfflineAndClose(this);
|
|
Session& s = entry.session();
|
|
|
|
// Reload the license, and save the header.
|
|
ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this));
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
vector<uint8_t> old_usage_header_2_ = encrypted_usage_header_;
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
vector<uint8_t> old_usage_header_1_ = encrypted_usage_header_;
|
|
vector<uint8_t> old_usage_entry_1 = s.encrypted_usage_entry();
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(s.close());
|
|
|
|
ShutDown();
|
|
Restart();
|
|
// Null pointer generates error.
|
|
ASSERT_NE(OEMCrypto_SUCCESS, OEMCrypto_LoadUsageTableHeader(
|
|
nullptr, old_usage_header_2_.size()));
|
|
ASSERT_NO_FATAL_FAILURE(s.open());
|
|
// Cannot load an entry if header didn't load.
|
|
ASSERT_EQ(OEMCrypto_ERROR_UNKNOWN_FAILURE,
|
|
OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(),
|
|
s.encrypted_usage_entry().data(),
|
|
s.encrypted_usage_entry().size()));
|
|
ASSERT_NO_FATAL_FAILURE(s.close());
|
|
|
|
// Modified header generates error.
|
|
vector<uint8_t> bad_header = encrypted_usage_header_;
|
|
bad_header[3] ^= 42;
|
|
ASSERT_NE(OEMCrypto_SUCCESS, LoadUsageTableHeader(bad_header));
|
|
ASSERT_NO_FATAL_FAILURE(s.open());
|
|
// Cannot load an entry if header didn't load.
|
|
ASSERT_EQ(OEMCrypto_ERROR_UNKNOWN_FAILURE,
|
|
OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(),
|
|
s.encrypted_usage_entry().data(),
|
|
s.encrypted_usage_entry().size()));
|
|
ASSERT_NO_FATAL_FAILURE(s.close());
|
|
|
|
// Old by 2 generation numbers is error.
|
|
ASSERT_EQ(OEMCrypto_ERROR_GENERATION_SKEW,
|
|
LoadUsageTableHeader(old_usage_header_2_));
|
|
ASSERT_NO_FATAL_FAILURE(s.open());
|
|
// Cannot load an entry if header didn't load.
|
|
ASSERT_NE(OEMCrypto_SUCCESS,
|
|
OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(),
|
|
s.encrypted_usage_entry().data(),
|
|
s.encrypted_usage_entry().size()));
|
|
ASSERT_NO_FATAL_FAILURE(s.close());
|
|
|
|
// Old by 1 generation numbers is just warning.
|
|
ASSERT_EQ(OEMCrypto_WARNING_GENERATION_SKEW,
|
|
LoadUsageTableHeader(old_usage_header_1_));
|
|
// Everything else should still work. The old entry goes with the old
|
|
// header.
|
|
ASSERT_NO_FATAL_FAILURE(s.open());
|
|
ASSERT_EQ(OEMCrypto_SUCCESS,
|
|
OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(),
|
|
old_usage_entry_1.data(),
|
|
old_usage_entry_1.size()));
|
|
ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s));
|
|
ASSERT_NO_FATAL_FAILURE(s.GenerateDerivedKeysFromSessionKey());
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, entry.license_messages().LoadResponse());
|
|
}
|
|
|
|
TEST_P(OEMCryptoUsageTableTest, LoadAndReloadEntries) {
|
|
constexpr size_t kEntryCount = 10;
|
|
std::vector<LicenseWithUsageEntry> entries(kEntryCount);
|
|
|
|
for (LicenseWithUsageEntry& entry : entries) {
|
|
entry.license_messages().set_api_version(license_api_version_);
|
|
}
|
|
|
|
for (size_t i = 0; i < kEntryCount; ++i) {
|
|
const std::string create_description =
|
|
"Creating entry #" + std::to_string(i);
|
|
// Create and update a new entry.
|
|
LicenseWithUsageEntry& new_entry = entries[i];
|
|
ASSERT_NO_FATAL_FAILURE(new_entry.MakeOfflineAndClose(this))
|
|
<< create_description;
|
|
// Reload all entries, starting with the most recently created.
|
|
for (size_t j = 0; j <= i; ++j) {
|
|
const std::string reload_description =
|
|
"Reloading entry #" + std::to_string(i - j) +
|
|
", after creating entry #" + std::to_string(i);
|
|
LicenseWithUsageEntry& old_entry = entries[i - j];
|
|
ASSERT_NO_FATAL_FAILURE(old_entry.session().open());
|
|
ASSERT_NO_FATAL_FAILURE(old_entry.ReloadUsageEntry())
|
|
<< reload_description;
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
old_entry.session().UpdateUsageEntry(&encrypted_usage_header_))
|
|
<< reload_description;
|
|
ASSERT_NO_FATAL_FAILURE(old_entry.session().close());
|
|
}
|
|
}
|
|
}
|
|
|
|
// A usage report with the wrong pst should fail.
|
|
TEST_P(OEMCryptoUsageTableTest, GenerateReportWrongPST) {
|
|
LicenseWithUsageEntry entry;
|
|
entry.license_messages().set_api_version(license_api_version_);
|
|
entry.MakeAndLoadOnline(this);
|
|
Session& s = entry.session();
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
s.GenerateReport("wrong_pst", OEMCrypto_ERROR_WRONG_PST));
|
|
}
|
|
|
|
// Test usage table timing.
|
|
TEST_P(OEMCryptoUsageTableTest, TimingTest) {
|
|
LicenseWithUsageEntry entry1;
|
|
entry1.license_messages().set_api_version(license_api_version_);
|
|
Session& s1 = entry1.session();
|
|
entry1.set_pst("my_pst_1");
|
|
ASSERT_NO_FATAL_FAILURE(entry1.MakeOfflineAndClose(this));
|
|
|
|
LicenseWithUsageEntry entry2;
|
|
entry2.license_messages().set_api_version(license_api_version_);
|
|
Session& s2 = entry2.session();
|
|
entry2.set_pst("my_pst_2");
|
|
ASSERT_NO_FATAL_FAILURE(entry2.MakeOfflineAndClose(this));
|
|
|
|
LicenseWithUsageEntry entry3;
|
|
entry3.license_messages().set_api_version(license_api_version_);
|
|
Session& s3 = entry3.session();
|
|
entry3.set_pst("my_pst_3");
|
|
ASSERT_NO_FATAL_FAILURE(entry3.MakeOfflineAndClose(this));
|
|
|
|
ASSERT_NO_FATAL_FAILURE(entry1.MakeOfflineAndClose(this));
|
|
ASSERT_NO_FATAL_FAILURE(entry2.MakeOfflineAndClose(this));
|
|
ASSERT_NO_FATAL_FAILURE(entry3.MakeOfflineAndClose(this));
|
|
|
|
wvutil::TestSleep::Sleep(kLongSleep);
|
|
ASSERT_NO_FATAL_FAILURE(entry1.OpenAndReload(this));
|
|
ASSERT_NO_FATAL_FAILURE(entry1.TestDecryptCTR());
|
|
|
|
ASSERT_NO_FATAL_FAILURE(entry2.OpenAndReload(this));
|
|
ASSERT_NO_FATAL_FAILURE(entry2.TestDecryptCTR());
|
|
|
|
wvutil::TestSleep::Sleep(kLongSleep);
|
|
ASSERT_NO_FATAL_FAILURE(entry1.TestDecryptCTR());
|
|
ASSERT_NO_FATAL_FAILURE(entry2.TestDecryptCTR());
|
|
|
|
wvutil::TestSleep::Sleep(kLongSleep);
|
|
ASSERT_NO_FATAL_FAILURE(entry1.DeactivateUsageEntry());
|
|
ASSERT_NO_FATAL_FAILURE(s1.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(s2.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(s1.close());
|
|
ASSERT_NO_FATAL_FAILURE(s2.close());
|
|
|
|
wvutil::TestSleep::Sleep(kLongSleep);
|
|
// This is as close to reboot as we can simulate in code.
|
|
ShutDown();
|
|
wvutil::TestSleep::Sleep(kShortSleep);
|
|
Restart();
|
|
ASSERT_EQ(OEMCrypto_SUCCESS,
|
|
OEMCrypto_LoadUsageTableHeader(encrypted_usage_header_.data(),
|
|
encrypted_usage_header_.size()));
|
|
|
|
// After a reboot, we should be able to reload keys, and generate reports.
|
|
wvutil::TestSleep::Sleep(kLongSleep);
|
|
ASSERT_NO_FATAL_FAILURE(entry2.OpenAndReload(this));
|
|
ASSERT_NO_FATAL_FAILURE(entry2.TestDecryptCTR());
|
|
ASSERT_NO_FATAL_FAILURE(s2.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(s2.close());
|
|
|
|
ASSERT_NO_FATAL_FAILURE(s1.open());
|
|
ASSERT_NO_FATAL_FAILURE(entry1.ReloadUsageEntry());
|
|
ASSERT_NO_FATAL_FAILURE(entry2.OpenAndReload(this));
|
|
ASSERT_NO_FATAL_FAILURE(entry3.OpenAndReload(this));
|
|
|
|
wvutil::TestSleep::Sleep(kLongSleep);
|
|
ASSERT_NO_FATAL_FAILURE(s1.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(entry1.GenerateVerifyReport(kInactiveUsed));
|
|
ASSERT_NO_FATAL_FAILURE(s2.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(entry2.GenerateVerifyReport(kActive));
|
|
ASSERT_NO_FATAL_FAILURE(s3.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(entry3.GenerateVerifyReport(kUnused));
|
|
}
|
|
|
|
// Verify the times in the usage report. For performance reasons, we allow
|
|
// the times in the usage report to be off by as much as kUsageTimeTolerance,
|
|
// which is 10 seconds. This acceptable error is called slop. This test needs
|
|
// to run long enough that the reported values are distinct, even after
|
|
// accounting for this slop.
|
|
TEST_P(OEMCryptoUsageTableTest, VerifyUsageTimes) {
|
|
LicenseWithUsageEntry entry;
|
|
entry.license_messages().set_api_version(license_api_version_);
|
|
entry.MakeAndLoadOnline(this);
|
|
Session& s = entry.session();
|
|
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused));
|
|
|
|
const int64_t kDotIntervalInSeconds = 5;
|
|
const int64_t kIdleInSeconds = 20;
|
|
const int64_t kPlaybackLoopInSeconds = 2 * 60;
|
|
|
|
cout << "This test verifies the elapsed time reported in the usage table "
|
|
"for a 2 minute simulated playback."
|
|
<< endl;
|
|
cout << "The total time for this test is about "
|
|
<< kPlaybackLoopInSeconds + 2 * kIdleInSeconds << " seconds." << endl;
|
|
cout << "Wait " << kIdleInSeconds
|
|
<< " seconds to verify usage table time before playback." << endl;
|
|
|
|
PrintDotsWhileSleep(kIdleInSeconds, kDotIntervalInSeconds);
|
|
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused));
|
|
cout << "Start simulated playback..." << endl;
|
|
|
|
int64_t dot_time = kDotIntervalInSeconds;
|
|
int64_t playback_time = 0;
|
|
const int64_t start_time = wvutil::Clock().GetCurrentTime();
|
|
do {
|
|
ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR());
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive));
|
|
wvutil::TestSleep::Sleep(kShortSleep);
|
|
playback_time = wvutil::Clock().GetCurrentTime() - start_time;
|
|
ASSERT_LE(0, playback_time);
|
|
if (playback_time >= dot_time) {
|
|
cout << ".";
|
|
cout.flush();
|
|
dot_time += kDotIntervalInSeconds;
|
|
}
|
|
} while (playback_time < kPlaybackLoopInSeconds);
|
|
cout << "\nSimulated playback time = " << playback_time << " seconds.\n";
|
|
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive));
|
|
EXPECT_NEAR(s.pst_report().seconds_since_first_decrypt() -
|
|
s.pst_report().seconds_since_last_decrypt(),
|
|
playback_time, kUsageTableTimeTolerance);
|
|
|
|
// We must update the usage entry BEFORE sleeping, not after.
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
|
|
cout << "Wait another " << kIdleInSeconds
|
|
<< " seconds "
|
|
"to verify usage table time since playback ended."
|
|
<< endl;
|
|
PrintDotsWhileSleep(kIdleInSeconds, kDotIntervalInSeconds);
|
|
|
|
// At this point, this is what we expect:
|
|
// idle playback loop idle
|
|
// |-----|-------------------------|-----|
|
|
// |<--->| = seconds_since_last_decrypt
|
|
// |<----------------------------->| = seconds_since_first_decrypt
|
|
// |<------------------------------------| = seconds_since_license_received
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive));
|
|
ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry());
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed));
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
entry.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE));
|
|
}
|
|
|
|
// This test class is only used to roll back the wall clock. It is used to
|
|
// verify that OEMCrypto's system clock is monotonic. It is should only be
|
|
// run on a device that allows an application to set the clock.
|
|
class OEMCryptoUsageTableTestWallClock : public OEMCryptoUsageTableTest {
|
|
public:
|
|
void SetUp() override { OEMCryptoUsageTableTest::SetUp(); }
|
|
|
|
void TearDown() override {
|
|
wvutil::TestSleep::ResetRollback();
|
|
OEMCryptoUsageTableTest::TearDown();
|
|
}
|
|
};
|
|
|
|
// NOTE: This test needs root access since clock_settime messes with the
|
|
// system time in order to verify that OEMCrypto protects against rollbacks in
|
|
// usage entries. Therefore, this test is filtered if not run as root. We
|
|
// don't test roll-forward protection or instances where the user rolls back
|
|
// the time to the last decrypt call since this requires hardware-secure
|
|
// clocks to guarantee.
|
|
//
|
|
// This test overlaps two tests in parallel because they each have several
|
|
// seconds of sleeping, then we roll the system clock back, and then we sleep
|
|
// some more.
|
|
// For the first test, we use entry1. The playback duration is 6 short
|
|
// intervals. We play for 3, roll the clock back 2, and then play for 3 more.
|
|
// We then sleep until after the allowed playback duration and try to play. If
|
|
// OEMCrypto allows the rollback, then there is only 5 intervals, which is
|
|
// legal. But if OEMCrypto forbids the rollback, then there is 8 intervals of
|
|
// playback, which is not legal.
|
|
//
|
|
// For the second test, we use entry2. The rental duration is 6 short
|
|
// intervals. The times are the same as for entry1, except we do not start
|
|
// playback for entry2 until the end.
|
|
|
|
// clang-format off
|
|
// [--][--][--][--][--][--][--] -- playback or rental limit.
|
|
//
|
|
// Here's what the system clock sees with rollback:
|
|
// [--][--][--] 3 short intervals of playback or sleep
|
|
// <------> Rollback 2 short intervals.
|
|
// [--][--][--] 3 short intervals of playback or sleep
|
|
// [--] 1 short intervals of sleep.
|
|
//
|
|
// Here's what the system clock sees without rollback:
|
|
// [--][--][--] 3 short intervals of playback or sleep
|
|
// [--][--][--] 3 short intervals of playback or sleep
|
|
// [--][--]X 2 short intervals of sleep.
|
|
//
|
|
// |<---------------------------->| 8 short intervals from license received
|
|
// until pst reports generated.
|
|
// clang-format on
|
|
|
|
TEST_P(OEMCryptoUsageTableTestWallClock, TimeRollbackPrevention) {
|
|
// This test may require root access. If user is not root, filter this test
|
|
// out.
|
|
if (!wvutil::TestSleep::CanChangeSystemTime()) {
|
|
GTEST_SKIP() << "Filtering out TimeRollbackPrevention.";
|
|
} else {
|
|
printf("Can change time. I will run TimeRollbackPrevention.\n");
|
|
}
|
|
cout << "This test temporarily rolls back the system time in order to "
|
|
"verify "
|
|
<< "that the usage report accounts for the change. After the test, it "
|
|
<< "rolls the clock back forward." << endl;
|
|
constexpr int kRollBackTime = kShortSleep * 2;
|
|
constexpr int kPlaybackCount = 3;
|
|
constexpr int kTotalTime = kShortSleep * 8;
|
|
|
|
LicenseWithUsageEntry entry1;
|
|
entry1.license_messages()
|
|
.core_response()
|
|
.timer_limits.total_playback_duration_seconds = 7 * kShortSleep;
|
|
entry1.MakeOfflineAndClose(this);
|
|
Session& s1 = entry1.session();
|
|
ASSERT_NO_FATAL_FAILURE(entry1.OpenAndReload(this));
|
|
|
|
LicenseWithUsageEntry entry2;
|
|
entry2.license_messages()
|
|
.core_response()
|
|
.timer_limits.rental_duration_seconds = 7 * kShortSleep;
|
|
entry2.MakeOfflineAndClose(this);
|
|
Session& s2 = entry2.session();
|
|
ASSERT_NO_FATAL_FAILURE(entry2.OpenAndReload(this));
|
|
|
|
// Start with three short intervals of playback for entry1.
|
|
for (int i = 0; i < kPlaybackCount; i++) {
|
|
ASSERT_NO_FATAL_FAILURE(entry1.TestDecryptCTR());
|
|
wvutil::TestSleep::Sleep(kShortSleep);
|
|
ASSERT_NO_FATAL_FAILURE(entry1.TestDecryptCTR());
|
|
}
|
|
|
|
cout << "Rolling the system time back..." << endl;
|
|
ASSERT_TRUE(wvutil::TestSleep::RollbackSystemTime(kRollBackTime));
|
|
|
|
// Three more short intervals of playback after the rollback.
|
|
for (int i = 0; i < kPlaybackCount; i++) {
|
|
ASSERT_NO_FATAL_FAILURE(entry1.TestDecryptCTR());
|
|
wvutil::TestSleep::Sleep(kShortSleep);
|
|
ASSERT_NO_FATAL_FAILURE(entry1.TestDecryptCTR());
|
|
}
|
|
|
|
// One short interval of sleep to push us past the 6 interval duration.
|
|
wvutil::TestSleep::Sleep(2 * kShortSleep);
|
|
|
|
// Should not be able to continue playback in entry1.
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
entry1.TestDecryptCTR(false, OEMCrypto_ERROR_KEY_EXPIRED));
|
|
// Should not be able to start playback in entry2.
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
entry2.TestDecryptCTR(true, OEMCrypto_ERROR_KEY_EXPIRED));
|
|
|
|
// Now we look at the usage reports:
|
|
ASSERT_NO_FATAL_FAILURE(s1.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(s2.UpdateUsageEntry(&encrypted_usage_header_));
|
|
|
|
ASSERT_NO_FATAL_FAILURE(s1.GenerateReport(entry1.pst()));
|
|
wvutil::Unpacked_PST_Report report1 = s1.pst_report();
|
|
EXPECT_EQ(report1.status(), kActive);
|
|
EXPECT_GE(report1.seconds_since_license_received(), kTotalTime);
|
|
EXPECT_GE(report1.seconds_since_first_decrypt(), kTotalTime);
|
|
|
|
ASSERT_NO_FATAL_FAILURE(s2.GenerateReport(entry2.pst()));
|
|
wvutil::Unpacked_PST_Report report2 = s2.pst_report();
|
|
EXPECT_EQ(report2.status(), kUnused);
|
|
EXPECT_GE(report2.seconds_since_license_received(), kTotalTime);
|
|
}
|
|
|
|
// Verify that a large PST can be used with usage table entries.
|
|
TEST_P(OEMCryptoUsageTableTest, PSTLargeBuffer) {
|
|
std::string pst(kMaxPSTLength, 'a'); // A large PST.
|
|
LicenseWithUsageEntry entry(pst);
|
|
entry.MakeOfflineAndClose(this);
|
|
Session& s = entry.session();
|
|
|
|
ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this));
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
entry.TestDecryptCTR()); // Should be able to decrypt.
|
|
ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); // Then deactivate.
|
|
// After deactivate, should not be able to decrypt.
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
entry.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE));
|
|
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
|
|
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed));
|
|
ASSERT_NO_FATAL_FAILURE(s.close());
|
|
}
|
|
|
|
// Verify that a usage entry with an invalid session cannot be used.
|
|
TEST_P(OEMCryptoUsageTableTest, UsageEntryWithInvalidSession) {
|
|
std::string pst("pst");
|
|
LicenseWithUsageEntry entry;
|
|
entry.license_messages().set_pst(pst);
|
|
|
|
entry.session().open();
|
|
ASSERT_NO_FATAL_FAILURE(entry.session().CreateNewUsageEntry());
|
|
entry.session().close();
|
|
ASSERT_EQ(OEMCrypto_ERROR_INVALID_SESSION,
|
|
OEMCrypto_DeactivateUsageEntry(
|
|
entry.session().session_id(),
|
|
reinterpret_cast<const uint8_t*>(pst.c_str()), pst.length()));
|
|
|
|
entry.session().open();
|
|
ASSERT_NO_FATAL_FAILURE(entry.session().CreateNewUsageEntry());
|
|
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) {
|
|
if (wvoec::global_features.api_version < 17) {
|
|
GTEST_SKIP() << "Test for versions 17 and up only.";
|
|
}
|
|
std::string pst("pst");
|
|
LicenseWithUsageEntry entry;
|
|
entry.license_messages().set_pst(pst);
|
|
|
|
entry.session().open();
|
|
ASSERT_NO_FATAL_FAILURE(entry.session().CreateNewUsageEntry());
|
|
entry.session().close();
|
|
ASSERT_EQ(OEMCrypto_ERROR_INVALID_SESSION,
|
|
OEMCrypto_ReuseUsageEntry(entry.session().session_id(), 0));
|
|
}
|
|
|
|
INSTANTIATE_TEST_SUITE_P(TestAll, OEMCryptoUsageTableTest,
|
|
Range<uint32_t>(kCoreMessagesAPI, kCurrentAPI + 1));
|
|
|
|
// These tests only work when the license has a core message.
|
|
INSTANTIATE_TEST_SUITE_P(TestAPI16, OEMCryptoUsageTableDefragTest,
|
|
Values<uint32_t>(kCurrentAPI));
|
|
|
|
// These tests only work when the license has a core message.
|
|
INSTANTIATE_TEST_SUITE_P(TestAPI16, OEMCryptoUsageTableTestWallClock,
|
|
Values<uint32_t>(kCurrentAPI));
|
|
|
|
/// @}
|
|
} // namespace wvoec
|