Files
ce_cdm/oemcrypto/test/oemcrypto_usage_table_test.cpp
2024-11-27 00:07:23 +00:00

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