// Copyright 2023 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. // #include "oemcrypto_usage_table_test.h" using ::testing::Range; using ::testing::Values; namespace wvoec { // Test that successive calls to PrepAndSignProvisioningRequest only increase // the provisioning count in the ODK message TEST_F(OEMCryptoSessionTests, Provisioning_IncrementCounterAPI18) { if (wvoec::global_features.api_version < 18) { GTEST_SKIP() << "Test for versions 18 and up only."; } // local struct to hold count values from core message typedef struct counts { uint32_t prov; uint32_t lic; uint32_t decrypt; uint64_t mgn; } counts; // prep and sign provisioning2 request, then extract counter values auto provision2 = [&](counts* c) { Session s; ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); provisioning_messages.PrepareSession(keybox_); provisioning_messages.SignAndVerifyRequest(); c->prov = provisioning_messages.core_request().counter_info.provisioning_count; c->lic = provisioning_messages.core_request().counter_info.license_count; c->decrypt = provisioning_messages.core_request().counter_info.decrypt_count; c->mgn = provisioning_messages.core_request() .counter_info.master_generation_number; }; // prep and sign provisioning4 request, then extract counter values auto provision4 = [&](counts* c) { // Same as SessionUtil::CreateProv4OEMKey, but we can't extract counter // values using that function Session s; ASSERT_NO_FATAL_FAILURE(s.open()); Provisioning40RoundTrip provisioning_messages(&s); ASSERT_NO_FATAL_FAILURE(provisioning_messages.PrepareSession(true)); ASSERT_NO_FATAL_FAILURE(s.SetPublicKeyFromSubjectPublicKey( provisioning_messages.oem_key_type(), provisioning_messages.oem_public_key().data(), provisioning_messages.oem_public_key().size())); wrapped_oem_key_ = provisioning_messages.wrapped_oem_key(); oem_public_key_ = provisioning_messages.oem_public_key(); oem_key_type_ = provisioning_messages.oem_key_type(); ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); ASSERT_EQ(OEMCrypto_SUCCESS, provisioning_messages.LoadOEMCertResponse()); c->prov = provisioning_messages.core_request().counter_info.provisioning_count; c->lic = provisioning_messages.core_request().counter_info.license_count; c->decrypt = provisioning_messages.core_request().counter_info.decrypt_count; c->mgn = provisioning_messages.core_request() .counter_info.master_generation_number; }; if (global_features.provisioning_method == OEMCrypto_OEMCertificate || global_features.provisioning_method == OEMCrypto_DrmCertificate) { GTEST_SKIP() << "Provisioning method does not increment prov counter"; } else if (global_features.provisioning_method == OEMCrypto_Keybox) { counts c1, c2; provision2(&c1); provision2(&c2); ASSERT_TRUE(c2.prov > c1.prov); ASSERT_TRUE(c2.lic == c1.lic); ASSERT_TRUE(c2.decrypt == c1.decrypt); ASSERT_TRUE(c2.mgn == c1.mgn); } else if (global_features.provisioning_method == OEMCrypto_BootCertificateChain) { counts c1, c2; provision4(&c1); provision4(&c2); ASSERT_TRUE(c2.prov > c1.prov); ASSERT_TRUE(c2.lic == c1.lic); ASSERT_TRUE(c2.decrypt == c1.decrypt); ASSERT_TRUE(c2.mgn == c1.mgn); } } // Test that successive calls to PrepAndSignLicenseRequest only increase // the license count in the ODK message TEST_F(OEMCryptoSessionTests, License_IncrementCounterAPI18) { if (wvoec::global_features.api_version < 18) { GTEST_SKIP() << "Test for versions 18 and up only."; } Session s; s.open(); LicenseRoundTrip license_messages(&s); InstallTestDrmKey(&s); ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); uint32_t prov_count1 = license_messages.core_request().counter_info.provisioning_count; uint32_t lic_count1 = license_messages.core_request().counter_info.license_count; uint32_t decrypt_count1 = license_messages.core_request().counter_info.decrypt_count; uint64_t master_generation_number1 = license_messages.core_request().counter_info.master_generation_number; Session s2; s2.open(); LicenseRoundTrip license_messages2(&s2); InstallTestDrmKey(&s2); ASSERT_NO_FATAL_FAILURE(license_messages2.SignAndVerifyRequest()); uint32_t prov_count2 = license_messages2.core_request().counter_info.provisioning_count; uint32_t lic_count2 = license_messages2.core_request().counter_info.license_count; uint32_t decrypt_count2 = license_messages2.core_request().counter_info.decrypt_count; uint64_t master_generation_number2 = license_messages2.core_request().counter_info.master_generation_number; ASSERT_TRUE(prov_count2 == prov_count1); ASSERT_TRUE(lic_count2 > lic_count1); ASSERT_TRUE(decrypt_count2 == decrypt_count1); ASSERT_TRUE(master_generation_number2 == master_generation_number1); } // Test that the license request includes the master generation number, and that // it is incremented correctly after usage table modification (save offline // license) and decrypt. Also test that decrypt count increments. TEST_F(OEMCryptoSessionTests, MasterGeneration_IncrementCounterAPI18) { if (wvoec::global_features.api_version < 18) { GTEST_SKIP() << "Test for versions 18 and up only."; } if (!OEMCrypto_SupportsUsageTable()) { GTEST_SKIP() << "Usage table not supported, so master generation number " "does not need to be checked."; } Session s1; s1.open(); LicenseRoundTrip license_messages(&s1); InstallTestDrmKey(&s1); ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); uint32_t prov_count1 = license_messages.core_request().counter_info.provisioning_count; uint32_t lic_count1 = license_messages.core_request().counter_info.license_count; uint32_t decrypt_count1 = license_messages.core_request().counter_info.decrypt_count; uint64_t master_generation_number1 = license_messages.core_request().counter_info.master_generation_number; // do the same as ReloadOfflineLicense to push the master generation number // up LicenseWithUsageEntry entry; entry.license_messages().set_api_version(kCurrentAPI); entry.MakeOfflineAndClose(this); Session& s = entry.session(); ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); Session s2; s2.open(); LicenseRoundTrip license_messages2(&s2); InstallTestDrmKey(&s2); ASSERT_NO_FATAL_FAILURE(license_messages2.SignAndVerifyRequest()); uint32_t prov_count2 = license_messages2.core_request().counter_info.provisioning_count; uint32_t lic_count2 = license_messages2.core_request().counter_info.license_count; uint32_t decrypt_count2 = license_messages2.core_request().counter_info.decrypt_count; uint64_t master_generation_number2 = license_messages2.core_request().counter_info.master_generation_number; ASSERT_TRUE(prov_count2 == prov_count1); ASSERT_TRUE(lic_count2 > lic_count1); ASSERT_TRUE(master_generation_number2 > master_generation_number1); // Log if decrypt counter hasn't gone up. Not a hard requirement, so don't // assert for it. if (decrypt_count2 <= decrypt_count1) { LOGE("Decrypt count did not increase."); } } TEST_P(OEMCryptoUsageTableTest, OEMCryptoMemoryLoadUsageEntryForHugeInvalidUsageEntryNumber) { LicenseWithUsageEntry entry; entry.license_messages().set_api_version(license_api_version_); // Make first entry 0. entry.MakeOfflineAndClose(this); Session s; s.open(); InstallTestDrmKey(&s); const uint32_t usage_entry_number = kHugeRandomNumber; ASSERT_NO_FATAL_FAILURE(OEMCrypto_LoadUsageEntry( s.session_id(), usage_entry_number, s.encrypted_usage_entry().data(), s.encrypted_usage_entry().size())); } // Test an online or streaming license with PST. This license requires a // valid nonce and can only be loaded once. TEST_P(OEMCryptoUsageTableTest, OnlineLicense) { LicenseWithUsageEntry entry; entry.license_messages().set_api_version(license_api_version_); entry.MakeAndLoadOnline(this); Session& s = entry.session(); ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); // test repeated report generation ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); // Flag the entry as inactive. ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); // It should report as inactive. ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); // Decrypt should fail. ASSERT_NO_FATAL_FAILURE( entry.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); // We could call DeactivateUsageEntry multiple times. The state should not // change. ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); // It should report as inactive. ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); } // Test the usage report when the license is loaded but the keys are never // used for decryption. TEST_P(OEMCryptoUsageTableTest, OnlineLicenseUnused) { LicenseWithUsageEntry entry; entry.license_messages().set_api_version(license_api_version_); entry.MakeAndLoadOnline(this); Session& s = entry.session(); ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); // No decrypt. We do not use this license. ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); // Flag the entry as inactive. ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); // It should report as inactive. ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUnused)); // Decrypt should fail. ASSERT_NO_FATAL_FAILURE( entry.TestDecryptCTR(true, OEMCrypto_ERROR_UNKNOWN_FAILURE)); // We could call DeactivateUsageEntry multiple times. The state should not // change. ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); // It should report as inactive. ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUnused)); } // Test that the usage table has been updated and saved before a report can be // generated. TEST_P(OEMCryptoUsageTableTest, ForbidReportWithNoUpdate) { LicenseWithUsageEntry entry; entry.license_messages().set_api_version(license_api_version_); entry.MakeAndLoadOnline(this); Session& s = entry.session(); ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); // Cannot generate a report without first updating the file. ASSERT_NO_FATAL_FAILURE( s.GenerateReport(entry.pst(), OEMCrypto_ERROR_ENTRY_NEEDS_UPDATE)); ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); // Now it's OK. ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); // Flag the entry as inactive. ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); // Cannot generate a report without first updating the file. ASSERT_NO_FATAL_FAILURE( s.GenerateReport(entry.pst(), OEMCrypto_ERROR_ENTRY_NEEDS_UPDATE)); // Decrypt should fail. ASSERT_NO_FATAL_FAILURE( entry.TestDecryptCTR(true, OEMCrypto_ERROR_UNKNOWN_FAILURE)); } // Test an online license with a license renewal. TEST_P(OEMCryptoUsageTableTest, OnlineLicenseWithRefreshAPI16) { LicenseWithUsageEntry entry; entry.license_messages().set_api_version(license_api_version_); entry.MakeAndLoadOnline(this); Session& s = entry.session(); ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); RenewalRoundTrip renewal_messages(&entry.license_messages()); MakeRenewalRequest(&renewal_messages); LoadRenewal(&renewal_messages, OEMCrypto_SUCCESS); ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); } // Verify that a streaming license cannot be reloaded. TEST_P(OEMCryptoUsageTableTest, RepeatOnlineLicense) { LicenseWithUsageEntry entry; entry.license_messages().set_api_version(license_api_version_); entry.MakeAndLoadOnline(this); Session& s = entry.session(); ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); ASSERT_NO_FATAL_FAILURE(s.close()); Session s2; ASSERT_NO_FATAL_FAILURE(s2.open()); ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s2)); s2.LoadUsageEntry(s); // Use the same entry. ASSERT_NE(OEMCrypto_SUCCESS, entry.license_messages().LoadResponse(&s2)); } // An offline license should not load on the first call if the nonce is bad. TEST_P(OEMCryptoUsageTableTest, OnlineBadNonce) { Session s; LicenseRoundTrip license_messages(&s); license_messages.set_api_version(license_api_version_); license_messages.set_control(wvoec::kControlNonceEnabled | wvoec::kControlNonceRequired); license_messages.set_pst("my-pst"); ASSERT_NO_FATAL_FAILURE(s.open()); ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s)); ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse()); for (uint32_t i = 0; i < license_messages.num_keys(); i++) license_messages.response_data().keys[i].control.nonce ^= 42; ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse()); ASSERT_EQ(OEMCrypto_ERROR_INVALID_NONCE, license_messages.LoadResponse()); } // A license with non-zero replay control bits needs a valid pst. TEST_P(OEMCryptoUsageTableTest, OnlineEmptyPST) { Session s; ASSERT_NO_FATAL_FAILURE(s.open()); ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s)); LicenseRoundTrip license_messages(&s); license_messages.set_api_version(license_api_version_); license_messages.set_control(wvoec::kControlNonceEnabled | wvoec::kControlNonceRequired); // DO NOT SET PST: license_messages.set_pst(pst); ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse()); ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse()); ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); ASSERT_NE(OEMCrypto_SUCCESS, license_messages.LoadResponse()); } // A license with non-zero replay control bits needs a valid pst. TEST_P(OEMCryptoUsageTableTest, OnlineMissingEntry) { Session s; ASSERT_NO_FATAL_FAILURE(s.open()); ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s)); LicenseRoundTrip license_messages(&s); license_messages.set_api_version(license_api_version_); license_messages.set_control(wvoec::kControlNonceEnabled | wvoec::kControlNonceRequired); license_messages.set_pst("my-pst"); ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse()); ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse()); // ENTRY NOT CREATED: ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); ASSERT_NE(OEMCrypto_SUCCESS, license_messages.LoadResponse()); } // Sessions should have at most one entry at a time. This tests different // orderings of CreateNewUsageEntry and LoadUsageEntry calls. TEST_P(OEMCryptoUsageTableTest, CreateAndLoadMultipleEntriesAPI16) { // Entry Count: we start each test with an empty header. uint32_t usage_entry_number; LicenseWithUsageEntry entry; entry.license_messages().set_api_version(license_api_version_); Session& s = entry.session(); // Make first entry 0. ASSERT_NO_FATAL_FAILURE(entry.MakeOfflineAndClose(this)); // Load an entry, then try to create a second. ASSERT_NO_FATAL_FAILURE(s.open()); // Reload entry 0. ASSERT_NO_FATAL_FAILURE(s.ReloadUsageEntry()); // Create new entry 1 should fail. ASSERT_NE(OEMCrypto_SUCCESS, OEMCrypto_CreateNewUsageEntry(entry.session().session_id(), &usage_entry_number)); ASSERT_NO_FATAL_FAILURE(s.close()); // Create an entry, then try to load a second. Session s2; ASSERT_NO_FATAL_FAILURE(s2.open()); // Create entry 1. ASSERT_NO_FATAL_FAILURE(s2.CreateNewUsageEntry()); // Try to reload entry 0. ASSERT_NE(OEMCrypto_SUCCESS, OEMCrypto_LoadUsageEntry(s2.session_id(), s.usage_entry_number(), s.encrypted_usage_entry().data(), s.encrypted_usage_entry().size())); ASSERT_NO_FATAL_FAILURE(s2.close()); // Reload an entry and a license, then try to load the same entry again. // This reloads entry 0. ASSERT_NO_FATAL_FAILURE(s.open()); ASSERT_NO_FATAL_FAILURE(s.ReloadUsageEntry()); ASSERT_NE(OEMCrypto_SUCCESS, OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(), s.encrypted_usage_entry().data(), s.encrypted_usage_entry().size())); ASSERT_NO_FATAL_FAILURE(s.close()); // Create an entry, then try to create a second entry. ASSERT_NO_FATAL_FAILURE(s2.open()); ASSERT_NO_FATAL_FAILURE(s2.CreateNewUsageEntry()); ASSERT_NE(OEMCrypto_SUCCESS, OEMCrypto_CreateNewUsageEntry( s2.session_id(), &usage_entry_number)); } // An entry can be loaded in only one session at a time. TEST_P(OEMCryptoUsageTableTest, LoadEntryInMultipleSessions) { // Entry Count: we start each test with an empty header. LicenseWithUsageEntry entry; entry.license_messages().set_api_version(license_api_version_); Session& s = entry.session(); // Make first entry 0. ASSERT_NO_FATAL_FAILURE(entry.MakeOfflineAndClose(this)); const uint32_t usage_entry_number = s.usage_entry_number(); EXPECT_EQ(usage_entry_number, 0u); // Should be only entry in this test. // Load an entry, then try to create a second. ASSERT_NO_FATAL_FAILURE(s.open()); // Reload entry 0. ASSERT_NO_FATAL_FAILURE(s.ReloadUsageEntry()); // Create an entry, then try to load a second. Session s2; ASSERT_NO_FATAL_FAILURE(s2.open()); // Try to load entry 0 into session 2. ASSERT_EQ(OEMCrypto_ERROR_INVALID_SESSION, OEMCrypto_LoadUsageEntry(s2.session_id(), usage_entry_number, s.encrypted_usage_entry().data(), s.encrypted_usage_entry().size())); } // Test generic encrypt when the license uses a PST. TEST_P(OEMCryptoUsageTableTest, GenericCryptoEncrypt) { if (!wvoec::global_features.generic_crypto) { GTEST_SKIP() << "Test for devices with generic crypto API only"; } LicenseWithUsageEntry entry; entry.license_messages().set_api_version(license_api_version_); entry.set_generic_crypto(true); entry.MakeAndLoadOnline(this); Session& s = entry.session(); OEMCryptoResult sts; unsigned int key_index = 0; vector 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) { if (!wvoec::global_features.generic_crypto) { GTEST_SKIP() << "Test for devices with generic crypto API only"; } LicenseWithUsageEntry entry; entry.license_messages().set_api_version(license_api_version_); entry.set_generic_crypto(true); entry.MakeAndLoadOnline(this); Session& s = entry.session(); OEMCryptoResult sts; unsigned int key_index = 1; vector 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) { if (!wvoec::global_features.generic_crypto) { GTEST_SKIP() << "Test for devices with generic crypto API only"; } LicenseWithUsageEntry entry; entry.license_messages().set_api_version(license_api_version_); entry.set_generic_crypto(true); entry.MakeAndLoadOnline(this); Session& s = entry.session(); OEMCryptoResult sts; unsigned int key_index = 2; vector 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) { if (!wvoec::global_features.generic_crypto) { GTEST_SKIP() << "Test for devices with generic crypto API only"; } LicenseWithUsageEntry entry; entry.license_messages().set_api_version(license_api_version_); entry.set_generic_crypto(true); entry.MakeAndLoadOnline(this); Session& s = entry.session(); OEMCryptoResult sts; unsigned int key_index = 3; vector 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)); } TEST_P(OEMCryptoUsageTableDefragTest, MakeAndMoveEntry) { // 1. Make an entry then close. LicenseWithUsageEntry entry; ASSERT_NO_FATAL_FAILURE(entry.set_pst("pst 0")); ASSERT_NO_FATAL_FAILURE(entry.MakeOfflineAndClose(this)); ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); ASSERT_NO_FATAL_FAILURE(entry.session().close()); // 2. Make an entry then immediately move it into the previous slot. // Not using helper functions because they shoehorn the session state into // a limited set of possibilities. We want to create the specific case of // immediately moving a newly created entry. // Like LicenseWithUsageEntry::MakeAndLoad() but stop after creating the new // usage entry. Session session; ASSERT_NO_FATAL_FAILURE(session.open()); ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&session)); LicenseRoundTrip license_messages_(&session); license_messages_.set_control(wvoec::kControlNonceOrEntry); ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); OEMCryptoResult result; ASSERT_NO_FATAL_FAILURE(session.CreateNewUsageEntry(&result)); // Not the same as Session::MoveUsageEntry, which opens and closes a session // around the move operation. We just want to call MoveEntry on the current // state. ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_MoveEntry(session.session_id(), 0)); ASSERT_NO_FATAL_FAILURE(session.close()); } // A usage table entry cannot be moved into an entry where an open session is // currently using the entry. TEST_P(OEMCryptoUsageTableDefragTest, MoveUsageEntriesToOpenSession) { LicenseWithUsageEntry entry0; entry0.set_pst("pst 0"); entry0.MakeOfflineAndClose(this); LicenseWithUsageEntry entry1; entry1.set_pst("pst 1"); entry1.MakeOfflineAndClose(this); entry0.session().open(); ASSERT_NO_FATAL_FAILURE(entry0.ReloadUsageEntry()); // s0 currently open on index 0. Expect this to fail: ASSERT_NO_FATAL_FAILURE(entry1.session().MoveUsageEntry( 0, &encrypted_usage_header_, OEMCrypto_ERROR_ENTRY_IN_USE)); } TEST_P(OEMCryptoUsageTableDefragTest, MoveUsageEntriesToInvalidHugeEntryIndex) { LicenseWithUsageEntry entry0; entry0.set_pst("pst 0"); entry0.MakeOfflineAndClose(this); entry0.session().open(); entry0.ReloadUsageEntry(); ASSERT_NO_FATAL_FAILURE( OEMCrypto_MoveEntry(entry0.session().session_id(), kHugeRandomNumber)); } // The usage table cannot be shrunk if any session is using an entry that // would be deleted. TEST_P(OEMCryptoUsageTableDefragTest, ShrinkOverOpenSessions) { LicenseWithUsageEntry entry0; entry0.set_pst("pst 0"); entry0.MakeOfflineAndClose(this); LicenseWithUsageEntry entry1; entry1.set_pst("pst 1"); entry1.MakeOfflineAndClose(this); entry0.session().open(); ASSERT_NO_FATAL_FAILURE(entry0.ReloadUsageEntry()); entry1.session().open(); ASSERT_NO_FATAL_FAILURE(entry1.ReloadUsageEntry()); // Since s0 and s1 are open, we can't shrink. ASSERT_NO_FATAL_FAILURE(ShrinkHeader(1, OEMCrypto_ERROR_ENTRY_IN_USE)); entry1.session().close(); // Can shrink after closing s1, even if s0 is open. ASSERT_NO_FATAL_FAILURE(ShrinkHeader(1, OEMCrypto_SUCCESS)); } // Verify the usage table size can be increased. TEST_P(OEMCryptoUsageTableDefragTest, EnlargeHeader) { LicenseWithUsageEntry entry0; entry0.set_pst("pst 0"); entry0.MakeOfflineAndClose(this); LicenseWithUsageEntry entry1; entry1.set_pst("pst 1"); entry1.MakeOfflineAndClose(this); // Can only shrink the header -- not make it bigger. ASSERT_NO_FATAL_FAILURE(ShrinkHeader(4, OEMCrypto_ERROR_UNKNOWN_FAILURE)); } // A new header can only be created while no entries are in use. TEST_P(OEMCryptoUsageTableDefragTest, CreateNewHeaderWhileUsingOldOne) { LicenseWithUsageEntry entry0; entry0.set_pst("pst 0"); entry0.MakeOfflineAndClose(this); LicenseWithUsageEntry entry1; entry1.set_pst("pst 1"); entry1.MakeOfflineAndClose(this); entry0.session().open(); ASSERT_NO_FATAL_FAILURE(entry0.ReloadUsageEntry()); const bool kExpectFailure = false; ASSERT_NO_FATAL_FAILURE(CreateUsageTableHeader(kExpectFailure)); } // Verify that a usage table entry can only be loaded into the correct index // of the table. TEST_P(OEMCryptoUsageTableDefragTest, ReloadUsageEntryWrongIndex) { LicenseWithUsageEntry entry0; entry0.set_pst("pst 0"); entry0.MakeOfflineAndClose(this); LicenseWithUsageEntry entry1; entry1.set_pst("pst 1"); entry1.MakeOfflineAndClose(this); entry0.session().set_usage_entry_number(1); ASSERT_NO_FATAL_FAILURE( FailReloadLicense(&entry0, OEMCrypto_ERROR_INVALID_SESSION)); } // Verify that a usage table entry cannot be loaded if it has been altered. TEST_P(OEMCryptoUsageTableDefragTest, ReloadUsageEntryBadData) { LicenseWithUsageEntry entry; entry.MakeOfflineAndClose(this); Session& s = entry.session(); ASSERT_NO_FATAL_FAILURE(s.open()); ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s)); vector 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) { if (wvoec::global_features.api_version < 17) { GTEST_SKIP() << "Test for versions 17 and up only."; } LicenseWithUsageEntry entry0; entry0.set_pst("pst 0"); LicenseWithUsageEntry entry1; entry1.set_pst("pst 1"); entry0.session().open(); ASSERT_NO_FATAL_FAILURE(entry0.session().CreateNewUsageEntry()); const uint32_t number = entry0.session().usage_entry_number(); entry0.session().close(); entry1.session().open(); ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_ReuseUsageEntry(entry1.session().session_id(), number)); } // Verify that usage entries cannot replace an entry that is currently in // use by a session. TEST_P(OEMCryptoUsageTableDefragTest, ReuseUsageEntryIndexInUseAPI17) { if (wvoec::global_features.api_version < 17) { GTEST_SKIP() << "Test for versions 17 and up only."; } LicenseWithUsageEntry entry0; entry0.set_pst("pst 0"); LicenseWithUsageEntry entry1; entry1.set_pst("pst 1"); entry0.session().open(); ASSERT_NO_FATAL_FAILURE(entry0.session().CreateNewUsageEntry()); const uint32_t number = entry0.session().usage_entry_number(); entry1.session().open(); ASSERT_EQ(OEMCrypto_ERROR_INVALID_SESSION, OEMCrypto_ReuseUsageEntry(entry1.session().session_id(), number)); } // Verify that usage entries cannot be created if the usage entry index is // too large. TEST_P(OEMCryptoUsageTableDefragTest, ReuseUsageEntryWithInvalidIndexAPI17) { if (wvoec::global_features.api_version < 17) { GTEST_SKIP() << "Test for versions 17 and up only."; } LicenseWithUsageEntry entry0; entry0.set_pst("pst 0"); LicenseWithUsageEntry entry1; entry1.set_pst("pst 1"); entry0.session().open(); ASSERT_NO_FATAL_FAILURE(entry0.session().CreateNewUsageEntry()); const uint32_t number = entry0.session().usage_entry_number(); entry0.session().close(); entry1.session().open(); ASSERT_EQ( OEMCrypto_ERROR_UNKNOWN_FAILURE, OEMCrypto_ReuseUsageEntry(entry1.session().session_id(), number + 42)); } // Verify that usage entries cannot be created if the session already has an // entry. TEST_P(OEMCryptoUsageTableDefragTest, ReuseUsageEntrySessionAlreadyHasEntryAPI17) { if (wvoec::global_features.api_version < 17) { GTEST_SKIP() << "Test for versions 17 and up only."; } LicenseWithUsageEntry entry; entry.set_pst("pst 0"); // Create 5 entries in the table. for (int i = 0; i < 5; i++) { entry.session().open(); ASSERT_NO_FATAL_FAILURE(entry.session().CreateNewUsageEntry()); entry.session().close(); } entry.session().open(); ASSERT_NO_FATAL_FAILURE(entry.session().CreateNewUsageEntry()); const uint32_t number = entry.session().usage_entry_number(); ASSERT_EQ( OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES, OEMCrypto_ReuseUsageEntry(entry.session().session_id(), number - 3)); } // This verifies that the usage table header can be loaded if the generation // number is off by one, but not off by two. TEST_P(OEMCryptoUsageTableTest, ReloadUsageTableWithSkew) { // This also tests a few other error conditions with usage table headers. LicenseWithUsageEntry entry; entry.MakeOfflineAndClose(this); Session& s = entry.session(); // Reload the license, and save the header. ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); vector 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) { // This test may require root access. If user is not root, filter this test // out. if (!wvutil::TestSleep::CanChangeSystemTime()) { GTEST_SKIP() << "Filtering out TimeRollbackPrevention."; } else { printf("Can change time. I will run TimeRollbackPrevention.\n"); } cout << "This test temporarily rolls back the system time in order to " "verify " << "that the usage report accounts for the change. After the test, it " << "rolls the clock back forward." << endl; constexpr int kRollBackTime = kShortSleep * 2; constexpr int kPlaybackCount = 3; constexpr int kTotalTime = kShortSleep * 8; LicenseWithUsageEntry entry1; entry1.license_messages() .core_response() .timer_limits.total_playback_duration_seconds = 7 * kShortSleep; entry1.MakeOfflineAndClose(this); Session& s1 = entry1.session(); ASSERT_NO_FATAL_FAILURE(entry1.OpenAndReload(this)); LicenseWithUsageEntry entry2; entry2.license_messages() .core_response() .timer_limits.rental_duration_seconds = 7 * kShortSleep; entry2.MakeOfflineAndClose(this); Session& s2 = entry2.session(); ASSERT_NO_FATAL_FAILURE(entry2.OpenAndReload(this)); // Start with three short intervals of playback for entry1. for (int i = 0; i < kPlaybackCount; i++) { ASSERT_NO_FATAL_FAILURE(entry1.TestDecryptCTR()); wvutil::TestSleep::Sleep(kShortSleep); ASSERT_NO_FATAL_FAILURE(entry1.TestDecryptCTR()); } cout << "Rolling the system time back..." << endl; ASSERT_TRUE(wvutil::TestSleep::RollbackSystemTime(kRollBackTime)); // Three more short intervals of playback after the rollback. for (int i = 0; i < kPlaybackCount; i++) { ASSERT_NO_FATAL_FAILURE(entry1.TestDecryptCTR()); wvutil::TestSleep::Sleep(kShortSleep); ASSERT_NO_FATAL_FAILURE(entry1.TestDecryptCTR()); } // One short interval of sleep to push us past the 6 interval duration. wvutil::TestSleep::Sleep(2 * kShortSleep); // Should not be able to continue playback in entry1. ASSERT_NO_FATAL_FAILURE( entry1.TestDecryptCTR(false, OEMCrypto_ERROR_KEY_EXPIRED)); // Should not be able to start playback in entry2. ASSERT_NO_FATAL_FAILURE( entry2.TestDecryptCTR(true, OEMCrypto_ERROR_KEY_EXPIRED)); // Now we look at the usage reports: ASSERT_NO_FATAL_FAILURE(s1.UpdateUsageEntry(&encrypted_usage_header_)); ASSERT_NO_FATAL_FAILURE(s2.UpdateUsageEntry(&encrypted_usage_header_)); ASSERT_NO_FATAL_FAILURE(s1.GenerateReport(entry1.pst())); wvutil::Unpacked_PST_Report report1 = s1.pst_report(); EXPECT_EQ(report1.status(), kActive); EXPECT_GE(report1.seconds_since_license_received(), kTotalTime); EXPECT_GE(report1.seconds_since_first_decrypt(), kTotalTime); ASSERT_NO_FATAL_FAILURE(s2.GenerateReport(entry2.pst())); wvutil::Unpacked_PST_Report report2 = s2.pst_report(); EXPECT_EQ(report2.status(), kUnused); EXPECT_GE(report2.seconds_since_license_received(), kTotalTime); } // Verify that a large PST can be used with usage table entries. TEST_P(OEMCryptoUsageTableTest, PSTLargeBuffer) { std::string pst(kMaxPSTLength, 'a'); // A large PST. LicenseWithUsageEntry entry(pst); entry.MakeOfflineAndClose(this); Session& s = entry.session(); ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); ASSERT_NO_FATAL_FAILURE( entry.TestDecryptCTR()); // Should be able to decrypt. ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); // Then deactivate. // After deactivate, should not be able to decrypt. ASSERT_NO_FATAL_FAILURE( entry.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); ASSERT_NO_FATAL_FAILURE(s.close()); } // Verify that a usage entry with an invalid session cannot be used. TEST_P(OEMCryptoUsageTableTest, UsageEntryWithInvalidSession) { std::string pst("pst"); LicenseWithUsageEntry entry; entry.license_messages().set_pst(pst); entry.session().open(); ASSERT_NO_FATAL_FAILURE(entry.session().CreateNewUsageEntry()); entry.session().close(); ASSERT_EQ(OEMCrypto_ERROR_INVALID_SESSION, OEMCrypto_DeactivateUsageEntry( entry.session().session_id(), reinterpret_cast(pst.c_str()), pst.length())); entry.session().open(); ASSERT_NO_FATAL_FAILURE(entry.session().CreateNewUsageEntry()); entry.session().close(); ASSERT_EQ(OEMCrypto_ERROR_INVALID_SESSION, OEMCrypto_MoveEntry(entry.session().session_id(), 0)); } // Verify that a usage entry with an invalid session cannot be used. TEST_P(OEMCryptoUsageTableTest, ReuseUsageEntryWithInvalidSessionAPI17) { if (wvoec::global_features.api_version < 17) { GTEST_SKIP() << "Test for versions 17 and up only."; } std::string pst("pst"); LicenseWithUsageEntry entry; entry.license_messages().set_pst(pst); entry.session().open(); ASSERT_NO_FATAL_FAILURE(entry.session().CreateNewUsageEntry()); entry.session().close(); ASSERT_EQ(OEMCrypto_ERROR_INVALID_SESSION, OEMCrypto_ReuseUsageEntry(entry.session().session_id(), 0)); } INSTANTIATE_TEST_SUITE_P(TestAll, OEMCryptoUsageTableTest, Range(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