Files
android/libwvdrmengine/oemcrypto/test/oemcrypto_usage_table_test.cpp
Fred Gylys-Colwell c53047bd1b Document huge buffer tests in separate group
Explain how the huge buffer tests work. In particular, state
that we expect that OEMCrypto returns an error on huge
buffers.

Staged here:
https://developers.devsite.corp.google.com/widevine/drm/client/oemcrypto/v18/oemcrypto-test/group/security

Bug: 300645748
Merged from https://widevine-internal-review.googlesource.com/188034

Change-Id: Iba9d652dfe57df6786b0ab60931f02f8ce9b8180
2024-02-01 13:40:52 -08:00

1750 lines
75 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 {
// Test that successive calls to PrepAndSignProvisioningRequest only increase
// the provisioning count in the ODK message
TEST_F(OEMCryptoSessionTests, Provisioning_IncrementCounterAPI18) {
// 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) {
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 (!OEMCrypto_SupportsUsageTable()) {
GTEST_SKIP() << "Usage table not supported, so master generation number "
"does not need to be checked.";
}
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(decrypt_count2 > decrypt_count1);
ASSERT_TRUE(master_generation_number2 > master_generation_number1);
}
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 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) {
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) {
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) {
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) {
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) {
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) {
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