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
This commit is contained in:
Alex Dale
2020-04-13 14:27:20 -07:00
parent e27bc4ba6a
commit 2a16d70a06
9 changed files with 160 additions and 7 deletions

View File

@@ -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);

View File

@@ -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

View File

@@ -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<int>(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) {

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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<CdmSecureStopId> 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<SubSampleInfo*> {

View File

@@ -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,

View File

@@ -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:

View File

@@ -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;