From 26aa378ca5f76de9f359e0a1d1c14f7f0f833848 Mon Sep 17 00:00:00 2001 From: Vicky Min Date: Mon, 27 Mar 2023 19:41:09 -0700 Subject: [PATCH] Refactor usage table tests Merge from Widevine repo of http://go/wvgerrit/169061 Bug: 253779846 Merged from https://widevine-internal-review.googlesource.com/167477 Change-Id: I6046e59449700c8be05641f71dcbb2bba6ce493b --- libwvdrmengine/oemcrypto/test/common.mk | 1 + .../oemcrypto/test/oemcrypto_test.cpp | 2050 +---------------- .../test/oemcrypto_usage_table_test.cpp | 1707 ++++++++++++++ .../test/oemcrypto_usage_table_test.h | 379 +++ 4 files changed, 2088 insertions(+), 2049 deletions(-) create mode 100644 libwvdrmengine/oemcrypto/test/oemcrypto_usage_table_test.cpp create mode 100644 libwvdrmengine/oemcrypto/test/oemcrypto_usage_table_test.h diff --git a/libwvdrmengine/oemcrypto/test/common.mk b/libwvdrmengine/oemcrypto/test/common.mk index 309abca9..dba1ee3b 100644 --- a/libwvdrmengine/oemcrypto/test/common.mk +++ b/libwvdrmengine/oemcrypto/test/common.mk @@ -19,6 +19,7 @@ LOCAL_SRC_FILES:= \ oemcrypto_basic_test.cpp \ oemcrypto_license_test.cpp \ oemcrypto_provisioning_test.cpp \ + oemcrypto_usage_table_test.cpp \ oemcrypto_test.cpp \ oemcrypto_test_android.cpp \ oemcrypto_test_main.cpp \ diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp index e23e9ae8..23c0040e 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp @@ -80,6 +80,7 @@ #include "oemcrypto_resource_test.h" #include "oemcrypto_session_tests_helper.h" #include "oemcrypto_types.h" +#include "oemcrypto_usage_table_test.h" #include "platform.h" #include "string_conversions.h" #include "test_sleep.h" @@ -1718,47 +1719,6 @@ INSTANTIATE_TEST_SUITE_P(TestHDCP, OEMCryptoSessionTestLoadCasKeysWithHDCP, // // Load, Refresh Keys Test // -class OEMCryptoRefreshTest : public OEMCryptoLicenseTest { - protected: - void SetUp() override { - OEMCryptoLicenseTest::SetUp(); - // These values allow us to run a few simple duration tests or just start - // playback right away. All times are in seconds since the license was - // signed. - // Soft expiry false means timers are strictly enforce. - timer_limits_.soft_enforce_rental_duration = true; - timer_limits_.soft_enforce_playback_duration = false; - // Playback may begin immediately. - timer_limits_.earliest_playback_start_seconds = 0; - // First playback may be within the first two seconds. - timer_limits_.rental_duration_seconds = kDuration; - // Once started, playback may last two seconds without a renewal. - timer_limits_.initial_renewal_duration_seconds = kDuration; - // Total playback is not limited. - timer_limits_.total_playback_duration_seconds = 0; - } - - void LoadLicense() { - license_messages_.core_response().timer_limits = timer_limits_; - ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); - ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); - ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); - ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); - } - - void MakeRenewalRequest(RenewalRoundTrip* renewal_messages) { - ASSERT_NO_FATAL_FAILURE(renewal_messages->SignAndVerifyRequest()); - ASSERT_NO_FATAL_FAILURE(renewal_messages->CreateDefaultResponse()); - } - - void LoadRenewal(RenewalRoundTrip* renewal_messages, - OEMCryptoResult expected_result) { - ASSERT_NO_FATAL_FAILURE(renewal_messages->EncryptAndSignResponse()); - ASSERT_EQ(expected_result, renewal_messages->LoadResponse()); - } - - ODK_TimerLimits timer_limits_; -}; // This class is for the refresh tests that should only be run on licenses with // a core message. @@ -4667,278 +4627,6 @@ TEST_F(OEMCryptoCastReceiverTest, TestSignaturePKCS1_15_20) { /// @addtogroup generic /// @{ -// This class is for testing the generic crypto functionality. -class OEMCryptoGenericCryptoTest : public OEMCryptoRefreshTest { - protected: - // buffer_size_ must be a multiple of encryption block size, 16. We'll use a - // reasonable number of blocks for most of the tests. - OEMCryptoGenericCryptoTest() : buffer_size_(160) {} - - void SetUp() override { - OEMCryptoRefreshTest::SetUp(); - ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); - ASSERT_NO_FATAL_FAILURE( - license_messages_.CreateResponseWithGenericCryptoKeys()); - InitializeClearBuffer(); - } - - void InitializeClearBuffer() { - clear_buffer_.assign(buffer_size_, 0); - for (size_t i = 0; i < clear_buffer_.size(); i++) { - clear_buffer_[i] = 1 + i % 250; - } - for (size_t i = 0; i < wvoec::KEY_IV_SIZE; i++) { - iv_[i] = i; - } - } - - void ResizeBuffer(size_t new_size) { - buffer_size_ = new_size; - InitializeClearBuffer(); // Re-initialize the clear buffer. - } - - void EncryptAndLoadKeys() { - ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); - ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); - } - - // Encrypt the buffer with the specified key made in - // CreateResponseWithGenericCryptoKeys. - void EncryptBuffer(unsigned int key_index, const vector& in_buffer, - vector* out_buffer) { - EncryptBufferWithKey(session_.license().keys[key_index].key_data, in_buffer, - out_buffer); - } - // Encrypt the buffer with the specified key. - void EncryptBufferWithKey(const uint8_t* key_data, - const vector& in_buffer, - vector* out_buffer) { - AES_KEY aes_key; - ASSERT_EQ(0, AES_set_encrypt_key(key_data, AES_BLOCK_SIZE * 8, &aes_key)); - uint8_t iv_buffer[wvoec::KEY_IV_SIZE]; - memcpy(iv_buffer, iv_, wvoec::KEY_IV_SIZE); - out_buffer->resize(in_buffer.size()); - ASSERT_GT(in_buffer.size(), 0u); - ASSERT_EQ(0u, in_buffer.size() % AES_BLOCK_SIZE); - AES_cbc_encrypt(in_buffer.data(), out_buffer->data(), in_buffer.size(), - &aes_key, iv_buffer, AES_ENCRYPT); - } - - // Sign the buffer with the specified key made in - // CreateResponseWithGenericCryptoKeys. - void SignBuffer(unsigned int key_index, const vector& in_buffer, - vector* signature) { - SignBufferWithKey(session_.license().keys[key_index].key_data, in_buffer, - signature); - } - - // Sign the buffer with the specified key. - void SignBufferWithKey(const uint8_t* key_data, - const vector& in_buffer, - vector* signature) { - unsigned int md_len = SHA256_DIGEST_LENGTH; - signature->resize(SHA256_DIGEST_LENGTH); - HMAC(EVP_sha256(), key_data, wvoec::MAC_KEY_SIZE, in_buffer.data(), - in_buffer.size(), signature->data(), &md_len); - } - - OEMCryptoResult GenericEncrypt(const uint8_t* key_handle, - size_t key_handle_length, - const uint8_t* clear_buffer, - size_t clear_buffer_length, const uint8_t* iv, - OEMCrypto_Algorithm algorithm, - uint8_t* out_buffer) { - if (ShouldGenerateCorpus()) { - const std::string file_name = - GetFileName("oemcrypto_generic_encrypt_fuzz_seed_corpus"); - OEMCrypto_Generic_Api_Fuzz fuzzed_structure; - fuzzed_structure.cipher_mode = OEMCrypto_CipherMode_CENC; - fuzzed_structure.algorithm = algorithm; - // Cipher mode and algorithm. - AppendToFile(file_name, reinterpret_cast(&fuzzed_structure), - sizeof(fuzzed_structure)); - AppendToFile(file_name, reinterpret_cast(iv), - wvoec::KEY_IV_SIZE); - AppendSeparator(file_name); - AppendToFile(file_name, reinterpret_cast(clear_buffer), - clear_buffer_length); - } - return OEMCrypto_Generic_Encrypt(key_handle, key_handle_length, - clear_buffer, clear_buffer_length, iv, - algorithm, out_buffer); - } - - OEMCryptoResult GenericDecrypt( - const uint8_t* key_handle, size_t key_handle_length, - const uint8_t* encrypted_buffer, size_t encrypted_buffer_length, - const uint8_t* iv, OEMCrypto_Algorithm algorithm, uint8_t* out_buffer) { - if (ShouldGenerateCorpus()) { - const std::string file_name = - GetFileName("oemcrypto_generic_decrypt_fuzz_seed_corpus"); - OEMCrypto_Generic_Api_Fuzz fuzzed_structure; - fuzzed_structure.cipher_mode = OEMCrypto_CipherMode_CENC; - fuzzed_structure.algorithm = algorithm; - // Cipher mode and algorithm. - AppendToFile(file_name, reinterpret_cast(&fuzzed_structure), - sizeof(fuzzed_structure)); - AppendToFile(file_name, reinterpret_cast(iv), - wvoec::KEY_IV_SIZE); - AppendSeparator(file_name); - AppendToFile(file_name, reinterpret_cast(encrypted_buffer), - encrypted_buffer_length); - } - return OEMCrypto_Generic_Decrypt(key_handle, key_handle_length, - encrypted_buffer, encrypted_buffer_length, - iv, algorithm, out_buffer); - } - - OEMCryptoResult GenericVerify(const uint8_t* key_handle, - size_t key_handle_length, - const uint8_t* clear_buffer, - size_t clear_buffer_length, - OEMCrypto_Algorithm algorithm, - const uint8_t* signature, - size_t signature_length) { - if (ShouldGenerateCorpus()) { - const std::string file_name = - GetFileName("oemcrypto_generic_verify_fuzz_seed_corpus"); - OEMCrypto_Generic_Api_Fuzz fuzzed_structure; - fuzzed_structure.cipher_mode = OEMCrypto_CipherMode_CENC; - fuzzed_structure.algorithm = algorithm; - // Cipher mode and algorithm. - AppendToFile(file_name, reinterpret_cast(&fuzzed_structure), - sizeof(fuzzed_structure)); - AppendToFile(file_name, reinterpret_cast(clear_buffer), - clear_buffer_length); - AppendSeparator(file_name); - AppendToFile(file_name, reinterpret_cast(signature), - signature_length); - } - return OEMCrypto_Generic_Verify(key_handle, key_handle_length, clear_buffer, - clear_buffer_length, algorithm, signature, - signature_length); - } - - OEMCryptoResult GenericSign(const uint8_t* key_handle, - size_t key_handle_length, - const uint8_t* clear_buffer, - size_t clear_buffer_length, - OEMCrypto_Algorithm algorithm, uint8_t* signature, - size_t* signature_length) { - if (ShouldGenerateCorpus()) { - const std::string file_name = - GetFileName("oemcrypto_generic_sign_fuzz_seed_corpus"); - OEMCrypto_Generic_Api_Fuzz fuzzed_structure; - fuzzed_structure.cipher_mode = OEMCrypto_CipherMode_CENC; - fuzzed_structure.algorithm = algorithm; - // Cipher mode and algorithm. - AppendToFile(file_name, reinterpret_cast(&fuzzed_structure), - sizeof(fuzzed_structure)); - AppendToFile(file_name, reinterpret_cast(clear_buffer), - clear_buffer_length); - } - return OEMCrypto_Generic_Sign(key_handle, key_handle_length, clear_buffer, - clear_buffer_length, algorithm, signature, - signature_length); - } - - // This asks OEMCrypto to encrypt with the specified key, and expects a - // failure. - void BadEncrypt(unsigned int key_index, OEMCrypto_Algorithm algorithm, - size_t buffer_length) { - OEMCryptoResult sts; - vector expected_encrypted; - EncryptBuffer(key_index, clear_buffer_, &expected_encrypted); - vector key_handle; - sts = GetKeyHandleIntoVector( - session_.session_id(), session_.license().keys[key_index].key_id, - session_.license().keys[key_index].key_id_length, - OEMCrypto_CipherMode_CENC, key_handle); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - vector encrypted(buffer_length); - sts = GenericEncrypt(key_handle.data(), key_handle.size(), - clear_buffer_.data(), buffer_length, iv_, algorithm, - encrypted.data()); - EXPECT_NE(OEMCrypto_SUCCESS, sts); - expected_encrypted.resize(buffer_length); - EXPECT_NE(encrypted, expected_encrypted); - } - - // This asks OEMCrypto to decrypt with the specified key, and expects a - // failure. - void BadDecrypt(unsigned int key_index, OEMCrypto_Algorithm algorithm, - size_t buffer_length) { - OEMCryptoResult sts; - vector encrypted; - EncryptBuffer(key_index, clear_buffer_, &encrypted); - vector key_handle; - sts = GetKeyHandleIntoVector( - session_.session_id(), session_.license().keys[key_index].key_id, - session_.license().keys[key_index].key_id_length, - OEMCrypto_CipherMode_CENC, key_handle); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - vector resultant(encrypted.size()); - sts = GenericDecrypt(key_handle.data(), key_handle.size(), encrypted.data(), - buffer_length, iv_, algorithm, resultant.data()); - EXPECT_NE(OEMCrypto_SUCCESS, sts); - EXPECT_NE(clear_buffer_, resultant); - } - - // This asks OEMCrypto to sign with the specified key, and expects a - // failure. - void BadSign(unsigned int key_index, OEMCrypto_Algorithm algorithm) { - OEMCryptoResult sts; - vector expected_signature; - SignBuffer(key_index, clear_buffer_, &expected_signature); - - vector key_handle; - sts = GetKeyHandleIntoVector( - session_.session_id(), session_.license().keys[key_index].key_id, - session_.license().keys[key_index].key_id_length, - OEMCrypto_CipherMode_CENC, key_handle); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - size_t signature_length = (size_t)SHA256_DIGEST_LENGTH; - vector signature(SHA256_DIGEST_LENGTH); - sts = GenericSign(key_handle.data(), key_handle.size(), - clear_buffer_.data(), clear_buffer_.size(), algorithm, - signature.data(), &signature_length); - EXPECT_NE(OEMCrypto_SUCCESS, sts); - EXPECT_NE(signature, expected_signature); - } - - // This asks OEMCrypto to verify a signature with the specified key, and - // expects a failure. - void BadVerify(unsigned int key_index, OEMCrypto_Algorithm algorithm, - size_t signature_size, bool alter_data) { - OEMCryptoResult sts; - vector signature; - SignBuffer(key_index, clear_buffer_, &signature); - if (alter_data) { - signature[0] ^= 42; - } - if (signature.size() < signature_size) { - signature.resize(signature_size); - } - - vector key_handle; - sts = GetKeyHandleIntoVector( - session_.session_id(), session_.license().keys[key_index].key_id, - session_.license().keys[key_index].key_id_length, - OEMCrypto_CipherMode_CENC, key_handle); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - sts = GenericVerify(key_handle.data(), key_handle.size(), - clear_buffer_.data(), clear_buffer_.size(), algorithm, - signature.data(), signature_size); - EXPECT_NE(OEMCrypto_SUCCESS, sts); - } - - // This must be a multiple of encryption block size. - size_t buffer_size_; - vector clear_buffer_; - vector encrypted_buffer_; - uint8_t iv_[wvoec::KEY_IV_SIZE]; -}; - TEST_P(OEMCryptoGenericCryptoTest, GenericKeyLoad) { EncryptAndLoadKeys(); } // Test that the Generic_Encrypt function works correctly. @@ -5501,1740 +5189,4 @@ INSTANTIATE_TEST_SUITE_P(TestAll, OEMCryptoGenericCryptoTest, INSTANTIATE_TEST_SUITE_P(TestAll, OEMCryptoGenericCryptoKeyIdLengthTest, Range(kCoreMessagesAPI, kCurrentAPI + 1)); /// @} - -/// @addtogroup usage_table -/// @{ - -class OEMCryptoUsageTableTest : public OEMCryptoGenericCryptoTest { - public: - void SetUp() override { OEMCryptoGenericCryptoTest::SetUp(); } - - virtual void ShutDown() { - ASSERT_NO_FATAL_FAILURE(session_.close()); - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Terminate()); - } - - virtual void Restart() { - OEMCrypto_SetSandbox(kTestSandbox, sizeof(kTestSandbox)); - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Initialize()); - (void)OEMCrypto_SetMaxAPIVersion(kCurrentAPI); - (void)OEMCrypto_EnterTestMode(); - EnsureTestROT(); - ASSERT_NO_FATAL_FAILURE(session_.open()); - } - - void PrintDotsWhileSleep(int64_t total_seconds, int64_t interval_seconds) { - int64_t dot_time = interval_seconds; - int64_t elapsed_time = 0; - const int64_t start_time = wvutil::Clock().GetCurrentTime(); - do { - wvutil::TestSleep::Sleep(1); - elapsed_time = wvutil::Clock().GetCurrentTime() - start_time; - if (elapsed_time >= dot_time) { - cout << "."; - cout.flush(); - dot_time += interval_seconds; - } - } while (elapsed_time < total_seconds); - cout << endl; - } - - OEMCryptoResult LoadUsageTableHeader( - const vector& encrypted_usage_header) { - return OEMCrypto_LoadUsageTableHeader(encrypted_usage_header.data(), - encrypted_usage_header.size()); - } -}; - -// 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) { - 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 expected_encrypted; - EncryptBufferWithKey(s.license().keys[key_index].key_data, clear_buffer_, - &expected_encrypted); - vector 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 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) { - 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 encrypted; - EncryptBufferWithKey(s.license().keys[key_index].key_data, clear_buffer_, - &encrypted); - vector 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 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) { - 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 expected_signature; - SignBufferWithKey(s.license().keys[key_index].key_data, clear_buffer_, - &expected_signature); - - vector 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(SHA256_DIGEST_LENGTH), gen_signature_length); - vector 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) { - 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 signature; - SignBufferWithKey(s.license().keys[key_index].key_data, clear_buffer_, - &signature); - - vector 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 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 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)); -} - -// 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 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> 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(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(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(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 old_usage_header_2_ = encrypted_usage_header_; - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - vector old_usage_header_1_ = encrypted_usage_header_; - vector 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 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 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(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(kCoreMessagesAPI, kCurrentAPI + 1)); - -// These tests only work when the license has a core message. -INSTANTIATE_TEST_SUITE_P(TestAPI16, OEMCryptoUsageTableDefragTest, - Values(kCurrentAPI)); - -// These tests only work when the license has a core message. -INSTANTIATE_TEST_SUITE_P(TestAPI16, OEMCryptoUsageTableTestWallClock, - Values(kCurrentAPI)); - -/// @} } // namespace wvoec diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_usage_table_test.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_usage_table_test.cpp new file mode 100644 index 00000000..8b78b14a --- /dev/null +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_usage_table_test.cpp @@ -0,0 +1,1707 @@ +// 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) { + // 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) { + 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 expected_encrypted; + EncryptBufferWithKey(s.license().keys[key_index].key_data, clear_buffer_, + &expected_encrypted); + vector 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 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) { + 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 encrypted; + EncryptBufferWithKey(s.license().keys[key_index].key_data, clear_buffer_, + &encrypted); + vector 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 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) { + 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 expected_signature; + SignBufferWithKey(s.license().keys[key_index].key_data, clear_buffer_, + &expected_signature); + + vector 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(SHA256_DIGEST_LENGTH), gen_signature_length); + vector 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) { + 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 signature; + SignBufferWithKey(s.license().keys[key_index].key_data, clear_buffer_, + &signature); + + vector 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 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 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)); +} + +// 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 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> 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(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(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(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 old_usage_header_2_ = encrypted_usage_header_; + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + vector old_usage_header_1_ = encrypted_usage_header_; + vector 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 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 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(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(kCoreMessagesAPI, kCurrentAPI + 1)); + +// These tests only work when the license has a core message. +INSTANTIATE_TEST_SUITE_P(TestAPI16, OEMCryptoUsageTableDefragTest, + Values(kCurrentAPI)); + +// These tests only work when the license has a core message. +INSTANTIATE_TEST_SUITE_P(TestAPI16, OEMCryptoUsageTableTestWallClock, + Values(kCurrentAPI)); + +/// @} +} // namespace wvoec \ No newline at end of file diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_usage_table_test.h b/libwvdrmengine/oemcrypto/test/oemcrypto_usage_table_test.h new file mode 100644 index 00000000..9ffd3fc9 --- /dev/null +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_usage_table_test.h @@ -0,0 +1,379 @@ +// Copyright 2023 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. +// +// Test data for OEMCrypto unit tests. +// +#ifndef CDM_OEMCRYPTO_USAGE_TABLE_TEST_ +#define CDM_OEMCRYPTO_USAGE_TABLE_TEST_ + +#include +#include +#include + +#include "OEMCryptoCENC.h" +#include "log.h" +#include "oemcrypto_basic_test.h" +#include "oemcrypto_license_test.h" +#include "test_sleep.h" + +namespace wvoec { + +class OEMCryptoRefreshTest : public OEMCryptoLicenseTest { + protected: + void SetUp() override { + OEMCryptoLicenseTest::SetUp(); + // These values allow us to run a few simple duration tests or just start + // playback right away. All times are in seconds since the license was + // signed. + // Soft expiry false means timers are strictly enforce. + timer_limits_.soft_enforce_rental_duration = true; + timer_limits_.soft_enforce_playback_duration = false; + // Playback may begin immediately. + timer_limits_.earliest_playback_start_seconds = 0; + // First playback may be within the first two seconds. + timer_limits_.rental_duration_seconds = kDuration; + // Once started, playback may last two seconds without a renewal. + timer_limits_.initial_renewal_duration_seconds = kDuration; + // Total playback is not limited. + timer_limits_.total_playback_duration_seconds = 0; + } + + void LoadLicense() { + license_messages_.core_response().timer_limits = timer_limits_; + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + } + + void MakeRenewalRequest(RenewalRoundTrip* renewal_messages) { + ASSERT_NO_FATAL_FAILURE(renewal_messages->SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(renewal_messages->CreateDefaultResponse()); + } + + void LoadRenewal(RenewalRoundTrip* renewal_messages, + OEMCryptoResult expected_result) { + ASSERT_NO_FATAL_FAILURE(renewal_messages->EncryptAndSignResponse()); + ASSERT_EQ(expected_result, renewal_messages->LoadResponse()); + } + + ODK_TimerLimits timer_limits_; +}; + +// This class is for testing the generic crypto functionality. +class OEMCryptoGenericCryptoTest : public OEMCryptoRefreshTest { + protected: + // buffer_size_ must be a multiple of encryption block size, 16. We'll use a + // reasonable number of blocks for most of the tests. + OEMCryptoGenericCryptoTest() : buffer_size_(160) {} + + void SetUp() override { + OEMCryptoRefreshTest::SetUp(); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE( + license_messages_.CreateResponseWithGenericCryptoKeys()); + InitializeClearBuffer(); + } + + void InitializeClearBuffer() { + clear_buffer_.assign(buffer_size_, 0); + for (size_t i = 0; i < clear_buffer_.size(); i++) { + clear_buffer_[i] = 1 + i % 250; + } + for (size_t i = 0; i < wvoec::KEY_IV_SIZE; i++) { + iv_[i] = i; + } + } + + void ResizeBuffer(size_t new_size) { + buffer_size_ = new_size; + InitializeClearBuffer(); // Re-initialize the clear buffer. + } + + void EncryptAndLoadKeys() { + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + } + + // Encrypt the buffer with the specified key made in + // CreateResponseWithGenericCryptoKeys. + void EncryptBuffer(unsigned int key_index, const vector& in_buffer, + vector* out_buffer) { + EncryptBufferWithKey(session_.license().keys[key_index].key_data, in_buffer, + out_buffer); + } + // Encrypt the buffer with the specified key. + void EncryptBufferWithKey(const uint8_t* key_data, + const vector& in_buffer, + vector* out_buffer) { + AES_KEY aes_key; + ASSERT_EQ(0, AES_set_encrypt_key(key_data, AES_BLOCK_SIZE * 8, &aes_key)); + uint8_t iv_buffer[wvoec::KEY_IV_SIZE]; + memcpy(iv_buffer, iv_, wvoec::KEY_IV_SIZE); + out_buffer->resize(in_buffer.size()); + ASSERT_GT(in_buffer.size(), 0u); + ASSERT_EQ(0u, in_buffer.size() % AES_BLOCK_SIZE); + AES_cbc_encrypt(in_buffer.data(), out_buffer->data(), in_buffer.size(), + &aes_key, iv_buffer, AES_ENCRYPT); + } + + // Sign the buffer with the specified key made in + // CreateResponseWithGenericCryptoKeys. + void SignBuffer(unsigned int key_index, const vector& in_buffer, + vector* signature) { + SignBufferWithKey(session_.license().keys[key_index].key_data, in_buffer, + signature); + } + + // Sign the buffer with the specified key. + void SignBufferWithKey(const uint8_t* key_data, + const vector& in_buffer, + vector* signature) { + unsigned int md_len = SHA256_DIGEST_LENGTH; + signature->resize(SHA256_DIGEST_LENGTH); + HMAC(EVP_sha256(), key_data, wvoec::MAC_KEY_SIZE, in_buffer.data(), + in_buffer.size(), signature->data(), &md_len); + } + + OEMCryptoResult GenericEncrypt(const uint8_t* key_handle, + size_t key_handle_length, + const uint8_t* clear_buffer, + size_t clear_buffer_length, const uint8_t* iv, + OEMCrypto_Algorithm algorithm, + uint8_t* out_buffer) { + if (ShouldGenerateCorpus()) { + const std::string file_name = + GetFileName("oemcrypto_generic_encrypt_fuzz_seed_corpus"); + OEMCrypto_Generic_Api_Fuzz fuzzed_structure; + fuzzed_structure.cipher_mode = OEMCrypto_CipherMode_CENC; + fuzzed_structure.algorithm = algorithm; + // Cipher mode and algorithm. + AppendToFile(file_name, reinterpret_cast(&fuzzed_structure), + sizeof(fuzzed_structure)); + AppendToFile(file_name, reinterpret_cast(iv), + wvoec::KEY_IV_SIZE); + AppendSeparator(file_name); + AppendToFile(file_name, reinterpret_cast(clear_buffer), + clear_buffer_length); + } + return OEMCrypto_Generic_Encrypt(key_handle, key_handle_length, + clear_buffer, clear_buffer_length, iv, + algorithm, out_buffer); + } + + OEMCryptoResult GenericDecrypt( + const uint8_t* key_handle, size_t key_handle_length, + const uint8_t* encrypted_buffer, size_t encrypted_buffer_length, + const uint8_t* iv, OEMCrypto_Algorithm algorithm, uint8_t* out_buffer) { + if (ShouldGenerateCorpus()) { + const std::string file_name = + GetFileName("oemcrypto_generic_decrypt_fuzz_seed_corpus"); + OEMCrypto_Generic_Api_Fuzz fuzzed_structure; + fuzzed_structure.cipher_mode = OEMCrypto_CipherMode_CENC; + fuzzed_structure.algorithm = algorithm; + // Cipher mode and algorithm. + AppendToFile(file_name, reinterpret_cast(&fuzzed_structure), + sizeof(fuzzed_structure)); + AppendToFile(file_name, reinterpret_cast(iv), + wvoec::KEY_IV_SIZE); + AppendSeparator(file_name); + AppendToFile(file_name, reinterpret_cast(encrypted_buffer), + encrypted_buffer_length); + } + return OEMCrypto_Generic_Decrypt(key_handle, key_handle_length, + encrypted_buffer, encrypted_buffer_length, + iv, algorithm, out_buffer); + } + + OEMCryptoResult GenericVerify(const uint8_t* key_handle, + size_t key_handle_length, + const uint8_t* clear_buffer, + size_t clear_buffer_length, + OEMCrypto_Algorithm algorithm, + const uint8_t* signature, + size_t signature_length) { + if (ShouldGenerateCorpus()) { + const std::string file_name = + GetFileName("oemcrypto_generic_verify_fuzz_seed_corpus"); + OEMCrypto_Generic_Api_Fuzz fuzzed_structure; + fuzzed_structure.cipher_mode = OEMCrypto_CipherMode_CENC; + fuzzed_structure.algorithm = algorithm; + // Cipher mode and algorithm. + AppendToFile(file_name, reinterpret_cast(&fuzzed_structure), + sizeof(fuzzed_structure)); + AppendToFile(file_name, reinterpret_cast(clear_buffer), + clear_buffer_length); + AppendSeparator(file_name); + AppendToFile(file_name, reinterpret_cast(signature), + signature_length); + } + return OEMCrypto_Generic_Verify(key_handle, key_handle_length, clear_buffer, + clear_buffer_length, algorithm, signature, + signature_length); + } + + OEMCryptoResult GenericSign(const uint8_t* key_handle, + size_t key_handle_length, + const uint8_t* clear_buffer, + size_t clear_buffer_length, + OEMCrypto_Algorithm algorithm, uint8_t* signature, + size_t* signature_length) { + if (ShouldGenerateCorpus()) { + const std::string file_name = + GetFileName("oemcrypto_generic_sign_fuzz_seed_corpus"); + OEMCrypto_Generic_Api_Fuzz fuzzed_structure; + fuzzed_structure.cipher_mode = OEMCrypto_CipherMode_CENC; + fuzzed_structure.algorithm = algorithm; + // Cipher mode and algorithm. + AppendToFile(file_name, reinterpret_cast(&fuzzed_structure), + sizeof(fuzzed_structure)); + AppendToFile(file_name, reinterpret_cast(clear_buffer), + clear_buffer_length); + } + return OEMCrypto_Generic_Sign(key_handle, key_handle_length, clear_buffer, + clear_buffer_length, algorithm, signature, + signature_length); + } + + // This asks OEMCrypto to encrypt with the specified key, and expects a + // failure. + void BadEncrypt(unsigned int key_index, OEMCrypto_Algorithm algorithm, + size_t buffer_length) { + OEMCryptoResult sts; + vector expected_encrypted; + EncryptBuffer(key_index, clear_buffer_, &expected_encrypted); + vector key_handle; + sts = GetKeyHandleIntoVector( + session_.session_id(), session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length, + OEMCrypto_CipherMode_CENC, key_handle); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + vector encrypted(buffer_length); + sts = GenericEncrypt(key_handle.data(), key_handle.size(), + clear_buffer_.data(), buffer_length, iv_, algorithm, + encrypted.data()); + EXPECT_NE(OEMCrypto_SUCCESS, sts); + expected_encrypted.resize(buffer_length); + EXPECT_NE(encrypted, expected_encrypted); + } + + // This asks OEMCrypto to decrypt with the specified key, and expects a + // failure. + void BadDecrypt(unsigned int key_index, OEMCrypto_Algorithm algorithm, + size_t buffer_length) { + OEMCryptoResult sts; + vector encrypted; + EncryptBuffer(key_index, clear_buffer_, &encrypted); + vector key_handle; + sts = GetKeyHandleIntoVector( + session_.session_id(), session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length, + OEMCrypto_CipherMode_CENC, key_handle); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + vector resultant(encrypted.size()); + sts = GenericDecrypt(key_handle.data(), key_handle.size(), encrypted.data(), + buffer_length, iv_, algorithm, resultant.data()); + EXPECT_NE(OEMCrypto_SUCCESS, sts); + EXPECT_NE(clear_buffer_, resultant); + } + + // This asks OEMCrypto to sign with the specified key, and expects a + // failure. + void BadSign(unsigned int key_index, OEMCrypto_Algorithm algorithm) { + OEMCryptoResult sts; + vector expected_signature; + SignBuffer(key_index, clear_buffer_, &expected_signature); + + vector key_handle; + sts = GetKeyHandleIntoVector( + session_.session_id(), session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length, + OEMCrypto_CipherMode_CENC, key_handle); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + size_t signature_length = (size_t)SHA256_DIGEST_LENGTH; + vector signature(SHA256_DIGEST_LENGTH); + sts = GenericSign(key_handle.data(), key_handle.size(), + clear_buffer_.data(), clear_buffer_.size(), algorithm, + signature.data(), &signature_length); + EXPECT_NE(OEMCrypto_SUCCESS, sts); + EXPECT_NE(signature, expected_signature); + } + + // This asks OEMCrypto to verify a signature with the specified key, and + // expects a failure. + void BadVerify(unsigned int key_index, OEMCrypto_Algorithm algorithm, + size_t signature_size, bool alter_data) { + OEMCryptoResult sts; + vector signature; + SignBuffer(key_index, clear_buffer_, &signature); + if (alter_data) { + signature[0] ^= 42; + } + if (signature.size() < signature_size) { + signature.resize(signature_size); + } + + vector key_handle; + sts = GetKeyHandleIntoVector( + session_.session_id(), session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length, + OEMCrypto_CipherMode_CENC, key_handle); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + sts = GenericVerify(key_handle.data(), key_handle.size(), + clear_buffer_.data(), clear_buffer_.size(), algorithm, + signature.data(), signature_size); + EXPECT_NE(OEMCrypto_SUCCESS, sts); + } + + // This must be a multiple of encryption block size. + size_t buffer_size_; + vector clear_buffer_; + vector encrypted_buffer_; + uint8_t iv_[wvoec::KEY_IV_SIZE]; +}; + +class OEMCryptoUsageTableTest : public OEMCryptoGenericCryptoTest { + public: + void SetUp() override { OEMCryptoGenericCryptoTest::SetUp(); } + + virtual void ShutDown() { + ASSERT_NO_FATAL_FAILURE(session_.close()); + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Terminate()); + } + + virtual void Restart() { + OEMCrypto_SetSandbox(kTestSandbox, sizeof(kTestSandbox)); + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Initialize()); + (void)OEMCrypto_SetMaxAPIVersion(kCurrentAPI); + (void)OEMCrypto_EnterTestMode(); + EnsureTestROT(); + ASSERT_NO_FATAL_FAILURE(session_.open()); + } + + void PrintDotsWhileSleep(int64_t total_seconds, int64_t interval_seconds) { + int64_t dot_time = interval_seconds; + int64_t elapsed_time = 0; + const int64_t start_time = wvutil::Clock().GetCurrentTime(); + do { + wvutil::TestSleep::Sleep(1); + elapsed_time = wvutil::Clock().GetCurrentTime() - start_time; + if (elapsed_time >= dot_time) { + cout << "."; + cout.flush(); + dot_time += interval_seconds; + } + } while (elapsed_time < total_seconds); + cout << endl; + } + + OEMCryptoResult LoadUsageTableHeader( + const vector& encrypted_usage_header) { + return OEMCrypto_LoadUsageTableHeader(encrypted_usage_header.data(), + encrypted_usage_header.size()); + } +}; + +} // namespace wvoec + +#endif // CDM_OEMCRYPTO_USAGE_TABLE_TEST_ \ No newline at end of file