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:
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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*> {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user