From 2a16d70a064765583ea6791a85386358743a5aa7 Mon Sep 17 00:00:00 2001 From: Alex Dale Date: Mon, 13 Apr 2020 14:27:20 -0700 Subject: [PATCH] Suppress error for removing lingering offline licenses. [ Merge of http://go/wvgerrit/97963 ] There are situations where an offline license file will remain on the system after it's usage entry has been deleted. This would result in its key set ID being reported as present by the CDM, but any operations acting upon it will result in an error. The app should be able to remove the license without error, so long as the license file exists and no other OEMCrypto operations fail. This change introduces a new error code LICENSE_USAGE_ENTRY_MISSING, which indicates that a license's usage entry cannot be found. A new integration test checks that the CDM can handle the calls to removeOfflineLicense(). Bug: 137034719 Test: Android unit and integration tests Change-Id: Ibdbe963b7f7e3ac97b446300d8e3896cdee7abc5 --- libwvdrmengine/cdm/core/include/cdm_session.h | 6 ++ .../cdm/core/include/wv_cdm_types.h | 1 + libwvdrmengine/cdm/core/src/cdm_engine.cpp | 28 +++-- libwvdrmengine/cdm/core/src/cdm_session.cpp | 22 ++++ .../cdm/core/test/test_printers.cpp | 3 + .../cdm/test/request_license_test.cpp | 101 ++++++++++++++++++ libwvdrmengine/include/WVErrors.h | 3 +- libwvdrmengine/include/mapErrors-inl.h | 2 + libwvdrmengine/include_hidl/mapErrors-inl.h | 1 + 9 files changed, 160 insertions(+), 7 deletions(-) diff --git a/libwvdrmengine/cdm/core/include/cdm_session.h b/libwvdrmengine/cdm/core/include/cdm_session.h index 8859093a..65f96955 100644 --- a/libwvdrmengine/cdm/core/include/cdm_session.h +++ b/libwvdrmengine/cdm/core/include/cdm_session.h @@ -233,6 +233,12 @@ class CdmSession { virtual CdmResponseType AddKeyInternal(const CdmKeyResponse& key_response); void UpdateRequestLatencyTiming(CdmResponseType sts); + // Checks that the usage entry in the usage table header matches the + // information of the currently loaded license for this session. + // Returns false if there is any unexpected mismatch of information, + // true otherwise. + bool VerifyOfflineUsageEntry(); + // These setters are for testing only. Takes ownership of the pointers. void set_license_parser(CdmLicense* license_parser); void set_crypto_session(CryptoSession* crypto_session); diff --git a/libwvdrmengine/cdm/core/include/wv_cdm_types.h b/libwvdrmengine/cdm/core/include/wv_cdm_types.h index f19248e0..c0205a70 100644 --- a/libwvdrmengine/cdm/core/include/wv_cdm_types.h +++ b/libwvdrmengine/cdm/core/include/wv_cdm_types.h @@ -411,6 +411,7 @@ enum CdmResponseType { LOAD_USAGE_ENTRY_INVALID_SESSION = 357, MOVE_USAGE_ENTRY_DESTINATION_IN_USE = 358, SHRINK_USAGE_TABLE_HEADER_ENTRY_IN_USE = 359, + LICENSE_USAGE_ENTRY_MISSING = 360, // Don't forget to add new values to // * core/test/test_printers.cpp. // * android/include/mapErrors-inl.h diff --git a/libwvdrmengine/cdm/core/src/cdm_engine.cpp b/libwvdrmengine/cdm/core/src/cdm_engine.cpp index ffaacbfd..66309966 100644 --- a/libwvdrmengine/cdm/core/src/cdm_engine.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_engine.cpp @@ -169,10 +169,13 @@ CdmResponseType CdmEngine::OpenKeySetSession( key_set_in_use = release_key_sets_.find(key_set_id) != release_key_sets_.end(); } - if (key_set_in_use) CloseKeySetSession(key_set_id); + if (key_set_in_use) { + LOGD("Reopening existing key session"); + CloseKeySetSession(key_set_id); + } CdmSessionId session_id; - CdmResponseType sts = + const CdmResponseType sts = OpenSession(KEY_SYSTEM, property_set, event_listener, nullptr /* forced_session_id */, &session_id); @@ -1114,14 +1117,19 @@ CdmResponseType CdmEngine::RemoveOfflineLicense( property_set.set_security_level( security_level == kSecurityLevelL3 ? kLevel3 : kLevelDefault); DeviceFiles handle(file_system_); + + if (!handle.Init(security_level)) { + LOGE("Cannot initialize device files: security_level = %s", + security_level == kSecurityLevelL3 ? "L3" : "Default"); + return REMOVE_OFFLINE_LICENSE_ERROR_1; + } + CdmResponseType sts = OpenKeySetSession(key_set_id, &property_set, nullptr /* event listener */); if (sts != NO_ERROR) { - if (!handle.Init(security_level)) { - LOGE("Cannot initialize device files"); - } + LOGE("Failed to open key set session: status = %d", static_cast(sts)); handle.DeleteLicense(key_set_id); - return REMOVE_OFFLINE_LICENSE_ERROR_1; + return sts; } CdmSessionId session_id; @@ -1141,6 +1149,14 @@ CdmResponseType CdmEngine::RemoveOfflineLicense( session_id = iter->second.first; sts = RemoveLicense(session_id); } + } else if (sts == LICENSE_USAGE_ENTRY_MISSING) { + // It is possible that the CDM is tracking a key set ID, but has + // removed the usage information associated with it. In this case, + // it will no longer be possible to load the license for release; + // and the file should simply be deleted. + LOGW("License usage entry is missing, deleting license file"); + handle.DeleteLicense(key_set_id); + sts = NO_ERROR; } if (sts != NO_ERROR) { diff --git a/libwvdrmengine/cdm/core/src/cdm_session.cpp b/libwvdrmengine/cdm/core/src/cdm_session.cpp index 33b066b4..ab32c568 100644 --- a/libwvdrmengine/cdm/core/src/cdm_session.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_session.cpp @@ -282,6 +282,9 @@ CdmResponseType CdmSession::RestoreOfflineSession(const CdmKeySetId& key_set_id, key_response_, &provider_session_token) || usage_table_header_ == nullptr) { provider_session_token.clear(); + } else if (!VerifyOfflineUsageEntry()) { + LOGE("License usage entry is invalid, cannot restore"); + return LICENSE_USAGE_ENTRY_MISSING; } else { CdmResponseType sts = usage_table_header_->LoadEntry( crypto_session_.get(), usage_entry_, usage_entry_number_); @@ -1131,6 +1134,25 @@ void CdmSession::UpdateRequestLatencyTiming(CdmResponseType sts) { license_request_latency_.Clear(); } +bool CdmSession::VerifyOfflineUsageEntry() { + // Check that the current license is the same as the expected + // entry in the usage table. It is possible that the license has + // been removed from the usage table but the license file remains. + if (usage_entry_number_ >= usage_table_header_->size()) { + LOGD("License usage entry does not exist: entry_number = %u, size = %zu", + usage_entry_number_, usage_table_header_->size()); + return false; + } + const CdmUsageEntryInfo& usage_entry_info = + usage_table_header_->usage_entry_info().at(usage_entry_number_); + if (usage_entry_info.storage_type != kStorageLicense || + usage_entry_info.key_set_id != key_set_id_) { + LOGD("License usage entry does not match"); + return false; + } + return true; +} + // For testing only - takes ownership of pointers void CdmSession::set_license_parser(CdmLicense* license_parser) { diff --git a/libwvdrmengine/cdm/core/test/test_printers.cpp b/libwvdrmengine/cdm/core/test/test_printers.cpp index 392b9d59..25bb54b9 100644 --- a/libwvdrmengine/cdm/core/test/test_printers.cpp +++ b/libwvdrmengine/cdm/core/test/test_printers.cpp @@ -521,6 +521,9 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { case LICENSE_RESPONSE_PARSE_ERROR_5: *os << "LICENSE_RESPONSE_PARSE_ERROR_5"; break; + case LICENSE_USAGE_ENTRY_MISSING: + *os << "LICENSE_USAGE_ENTRY_MISSING"; + break; case LIST_LICENSE_ERROR_1: *os << "LIST_LICENSE_ERROR_1"; break; diff --git a/libwvdrmengine/cdm/test/request_license_test.cpp b/libwvdrmengine/cdm/test/request_license_test.cpp index 8a5d64f1..ed36edee 100644 --- a/libwvdrmengine/cdm/test/request_license_test.cpp +++ b/libwvdrmengine/cdm/test/request_license_test.cpp @@ -5997,6 +5997,107 @@ TEST_F(WvCdmRequestLicenseTest, DISABLED_DecryptPathTest) { decryptor_->CloseSession(session_id_); } +// This tests checks that if a valid offline license file is found on +// the device but is missing the usage entry associated with it, that +// the CDM can still remove the license without issuing an error back +// to the calling app. +// This checks two cases: +// 1) The license's entry is outside the range of the table +// 2) The entry in the usage table that the license points to does +// not match the license's key set ID (possible for entry to have +// been overwritten). +TEST_F(WvCdmRequestLicenseTest, RemoveOfflineLicenseWithMissingUsageEntry) { + Unprovision(); + Provision(); + const CdmSecurityLevel security_level = GetDefaultSecurityLevel(); + + FileSystem file_system; + DeviceFiles handle(&file_system); + EXPECT_TRUE(handle.Init(security_level)); + + std::string key_id; + std::string client_auth; + GetOfflineConfiguration(&key_id, &client_auth); + + // Setup: Request an offline license to create a valid license file. + // This license will be used to test how the CDM handles request to + // remove it when its entry has been deleted. + + decryptor_->OpenSession(config_.key_system(), nullptr, kDefaultCdmIdentifier, + nullptr, &session_id_); + GenerateKeyRequest(key_id, kLicenseTypeOffline); + VerifyKeyRequestResponse(config_.license_server(), client_auth); + // Save the key set ID for check below. + const std::string original_key_set_id(key_set_id_); + decryptor_->CloseSession(session_id_); + + // Retrieve license from storage for later. + DeviceFiles::CdmLicenseData license_data; + DeviceFiles::ResponseType sub_result = DeviceFiles::kNoError; + EXPECT_TRUE( + handle.RetrieveLicense(original_key_set_id, &license_data, &sub_result)); + EXPECT_EQ(DeviceFiles::kNoError, sub_result); + + // Re-provision. + Unprovision(); + handle.DeleteAllFiles(); + Provision(); + + // Part 1: Test when usage entry is out of range of the table. + + // Store license from earlier, this will cause ListStoredLicenses() to + // return the key set ID of the setup license. + EXPECT_TRUE(handle.StoreLicense(license_data, &sub_result)); + EXPECT_EQ(DeviceFiles::kNoError, sub_result); + + std::vector key_set_ids; + EXPECT_EQ(NO_ERROR, decryptor_->ListStoredLicenses( + security_level, kDefaultCdmIdentifier, &key_set_ids)); + // Note: It is possible that future changes to the CDM will cause this + // check to fail (such by filtering results from ListStoreLicenses). + EXPECT_THAT(key_set_ids, Contains(original_key_set_id)); + + EXPECT_EQ(NO_ERROR, + decryptor_->RemoveOfflineLicense( + original_key_set_id, security_level, kDefaultCdmIdentifier)); + + // Verify license has been removed. + EXPECT_EQ(NO_ERROR, decryptor_->ListStoredLicenses( + security_level, kDefaultCdmIdentifier, &key_set_ids)); + EXPECT_THAT(key_set_ids, Not(Contains(original_key_set_id))); + + // Re-provision. + Unprovision(); + handle.DeleteAllFiles(); + Provision(); + + // Part 2: Test when the entry does not match the license's key set ID. + + EXPECT_TRUE(handle.StoreLicense(license_data, &sub_result)); + EXPECT_EQ(DeviceFiles::kNoError, sub_result); + + // Request another license so that the usage table is not empty. + decryptor_->OpenSession(config_.key_system(), nullptr, kDefaultCdmIdentifier, + nullptr, &session_id_); + GenerateKeyRequest(key_id, kLicenseTypeOffline); + VerifyKeyRequestResponse(config_.license_server(), client_auth); + decryptor_->CloseSession(session_id_); + + // Get list of existing offline licenses. + EXPECT_EQ(NO_ERROR, decryptor_->ListStoredLicenses( + security_level, kDefaultCdmIdentifier, &key_set_ids)); + EXPECT_THAT(key_set_ids, Contains(original_key_set_id)); + + EXPECT_EQ(NO_ERROR, + decryptor_->RemoveOfflineLicense( + original_key_set_id, security_level, kDefaultCdmIdentifier)); + + // Verify license has been removed. + EXPECT_EQ(NO_ERROR, decryptor_->ListStoredLicenses( + security_level, kDefaultCdmIdentifier, &key_set_ids)); + EXPECT_THAT(key_set_ids, Not(Contains(original_key_set_id))); +} + class WvCdmRequestLicenseRollbackTest : public WvCdmRequestLicenseTest, public ::testing::WithParamInterface { diff --git a/libwvdrmengine/include/WVErrors.h b/libwvdrmengine/include/WVErrors.h index 38ef22c8..d0bf031c 100644 --- a/libwvdrmengine/include/WVErrors.h +++ b/libwvdrmengine/include/WVErrors.h @@ -292,10 +292,11 @@ enum { kLoadUsageEntryInvalidSession = ERROR_DRM_VENDOR_MIN + 309, kMoveUsageEntryDestinationInUse = ERROR_DRM_VENDOR_MIN + 310, kShrinkUsageTableHeaderEntryInUse = ERROR_DRM_VENDOR_MIN + 311, + kLicenseUsageEntryMissing = ERROR_DRM_VENDOR_MIN + 312, // This should always follow the last error code. // The offset value should be updated each time a new error code is added. - kErrorWVDrmMaxErrorUsed = ERROR_DRM_VENDOR_MIN + 311, + kErrorWVDrmMaxErrorUsed = ERROR_DRM_VENDOR_MIN + 312, // Used by crypto test mode kErrorTestMode = ERROR_DRM_VENDOR_MAX, diff --git a/libwvdrmengine/include/mapErrors-inl.h b/libwvdrmengine/include/mapErrors-inl.h index bc98d9e5..c86726ed 100644 --- a/libwvdrmengine/include/mapErrors-inl.h +++ b/libwvdrmengine/include/mapErrors-inl.h @@ -356,6 +356,8 @@ static android::status_t mapCdmResponseType(wvcdm::CdmResponseType res) { return kLicenseResponseParseError4; case wvcdm::LICENSE_RESPONSE_PARSE_ERROR_5: return kLicenseResponseParseError5; + case wvcdm::LICENSE_USAGE_ENTRY_MISSING: + return kLicenseUsageEntryMissing; case wvcdm::LIST_LICENSE_ERROR_1: return kListLicenseError1; case wvcdm::LIST_LICENSE_ERROR_2: diff --git a/libwvdrmengine/include_hidl/mapErrors-inl.h b/libwvdrmengine/include_hidl/mapErrors-inl.h index 1f861c0d..dfd4daa4 100644 --- a/libwvdrmengine/include_hidl/mapErrors-inl.h +++ b/libwvdrmengine/include_hidl/mapErrors-inl.h @@ -358,6 +358,7 @@ static Status mapCdmResponseType(wvcdm::CdmResponseType res) { case wvcdm::LOAD_USAGE_ENTRY_INVALID_SESSION: case wvcdm::MOVE_USAGE_ENTRY_DESTINATION_IN_USE: case wvcdm::SHRINK_USAGE_TABLE_HEADER_ENTRY_IN_USE: + case wvcdm::LICENSE_USAGE_ENTRY_MISSING: ALOGW("Returns UNKNOWN error for legacy status: %d", res); return Status::ERROR_DRM_UNKNOWN;