Recovery from usage info corruption

[ Merge of http://go/wvgerrit/46623 ]

If corruption of the usage information file is detected while saving a
streaming license with a PST, usage information file is deleted, so that
a subsequent load keys may succeed.

Also when calling the MediaDrm API releaseAllSecureStops(), an error would
be returned if usage info file was corrupted. Since this file is
deleted successfully, errors have been replaced with warnings.

Bug: 73447733
Test: wv unit/integration tests
Change-Id: Ie4a63ac202fd6009609105f38ffa8a3b23ed334e
This commit is contained in:
Rahul Frias
2018-04-01 00:27:49 -07:00
parent 4e201c2700
commit d7d8940174
8 changed files with 299 additions and 16 deletions

View File

@@ -289,7 +289,7 @@ enum CdmResponseType {
USAGE_STORE_LICENSE_FAILED = 247,
USAGE_STORE_USAGE_INFO_FAILED = 248,
USAGE_INVALID_LOAD_ENTRY = 249,
REMOVE_ALL_USAGE_INFO_ERROR_4 = 250,
/* previously REMOVE_ALL_USAGE_INFO_ERROR_4 = 250, */
REMOVE_ALL_USAGE_INFO_ERROR_5 = 251,
RELEASE_USAGE_INFO_FAILED = 252,
INCORRECT_USAGE_SUPPORT_TYPE_1 = 253,

View File

@@ -1223,24 +1223,29 @@ CdmResponseType CdmEngine::RemoveAllUsageInfo(const std::string& app_id) {
if (!handle.RetrieveUsageInfo(
DeviceFiles::GetUsageInfoFileName(app_id),
&usage_data)) {
status = REMOVE_ALL_USAGE_INFO_ERROR_4;
LOGW("CdmEngine::RemoveAllUsageInfo: failed to retrieve usage info");
break;
}
if (usage_data.empty()) break;
status = usage_session_->DeleteUsageEntry(
CdmResponseType res = usage_session_->DeleteUsageEntry(
usage_data[0].usage_entry_number);
if (status != NO_ERROR) break;
if (res != NO_ERROR) {
LOGW("CdmEngine::RemoveAllUsageInfo: failed to delete usage "
"entry: error: %d", res);
break;
}
if (!handle.DeleteUsageInfo(
DeviceFiles::GetUsageInfoFileName(app_id),
usage_data[0].provider_session_token)) {
status = REMOVE_ALL_USAGE_INFO_ERROR_6;
LOGW("CdmEngine::RemoveAllUsageInfo: failed to delete usage "
"info");
break;
}
} while (status == NO_ERROR && !usage_data.empty());
} while (!usage_data.empty());
std::vector<std::string> provider_session_tokens;
if (!handle.DeleteAllUsageInfoForApp(

View File

@@ -841,6 +841,25 @@ CdmResponseType CdmSession::StoreLicense() {
key_set_id_, usage_entry_,
usage_entry_number_)) {
LOGE("CdmSession::StoreLicense: Unable to store usage info");
// Usage info file is corrupt. Delete current usage entry and file.
switch (usage_support_type_) {
case kUsageEntrySupport:
DeleteUsageEntry(usage_entry_number_);
break;
case kUsageTableSupport:
crypto_session_->DeleteUsageInformation(provider_session_token);
crypto_session_->UpdateUsageInformation();
break;
default:
LOGW("CdmSession::StoreLicense: unexpected usage support type: %d",
usage_support_type_);
break;
}
std::vector<std::string> provider_session_tokens;
file_handle_->DeleteAllUsageInfoForApp(
DeviceFiles::GetUsageInfoFileName(app_id),
&provider_session_tokens);
return STORE_USAGE_INFO_ERROR;
}
return NO_ERROR;

View File

@@ -227,9 +227,6 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) {
case REMOVE_ALL_USAGE_INFO_ERROR_2:
*os << "REMOVE_ALL_USAGE_INFO_ERROR_2";
break;
case REMOVE_ALL_USAGE_INFO_ERROR_4:
*os << "REMOVE_ALL_USAGE_INFO_ERROR_4";
break;
case REMOVE_ALL_USAGE_INFO_ERROR_5:
*os << "REMOVE_ALL_USAGE_INFO_ERROR_5";
break;

View File

@@ -1475,13 +1475,11 @@ class WvCdmRequestLicenseTest : public WvCdmTestBase {
void VerifyKeyRequestResponse(const std::string& server_url,
const std::string& client_auth) {
std::string response;
VerifyKeyRequestResponse(server_url, client_auth, false);
}
void VerifyUsageKeyRequestResponse(const std::string& server_url,
const std::string& client_auth) {
std::string response;
VerifyKeyRequestResponse(server_url, client_auth, true);
}
@@ -1496,10 +1494,19 @@ class WvCdmRequestLicenseTest : public WvCdmTestBase {
const std::string& client_auth,
bool is_usage,
std::string* response) {
VerifyKeyRequestResponse(wvcdm::KEY_ADDED, server_url, client_auth,
is_usage, response);
}
void VerifyKeyRequestResponse(CdmResponseType expected_response,
const std::string& server_url,
const std::string& client_auth,
bool is_usage,
std::string* response) {
*response = GetKeyRequestResponse(server_url, client_auth);
EXPECT_EQ(decryptor_.AddKey(session_id_, *response, &key_set_id_),
wvcdm::KEY_ADDED);
expected_response);
EXPECT_EQ(is_usage || license_type_ == kLicenseTypeOffline,
key_set_id_.size() > 0);
}
@@ -3116,6 +3123,156 @@ TEST_F(WvCdmRequestLicenseTest, UsageRemoveAllTest) {
EXPECT_TRUE(usage_info.empty());
}
TEST_F(WvCdmRequestLicenseTest, RemoveCorruptedUsageInfoTest) {
Unprovision();
std::string app_id_empty = "";
std::string app_id_not_empty = "not empty";
TestWvCdmClientPropertySet property_set;
Provision(kLevelDefault);
CdmSecurityLevel security_level = GetDefaultSecurityLevel();
FileSystem file_system;
DeviceFiles handle(&file_system);
EXPECT_TRUE(handle.Init(security_level));
std::vector<std::string> psts;
EXPECT_TRUE(handle.DeleteAllUsageInfoForApp(
DeviceFiles::GetUsageInfoFileName(""), &psts));
for (size_t i = 0; i < N_ELEM(usage_info_sub_samples_icp); ++i) {
SubSampleInfo* data = usage_info_sub_samples_icp + i;
property_set.set_app_id(i % 2 == 0 ? app_id_empty : app_id_not_empty);
decryptor_.OpenSession(g_key_system, &property_set, kDefaultCdmIdentifier,
NULL, &session_id_);
std::string key_id = a2bs_hex(
"000000427073736800000000" // blob size and pssh
"EDEF8BA979D64ACEA3C827DCD51D21ED00000022" // Widevine system id
"08011a0d7769646576696e655f74657374220f73" // pssh data
"747265616d696e675f636c6970");
char ch = 0x33 + i;
key_id.append(1, ch);
GenerateKeyRequest(key_id, kLicenseTypeStreaming, &property_set);
// TODO(rfrias): streaming_clip6 is a streaming license without a pst
if (ch == '6')
VerifyKeyRequestResponse(g_license_server, g_client_auth, false);
else
VerifyUsageKeyRequestResponse(g_license_server, g_client_auth);
std::vector<uint8_t> decrypt_buffer(data->encrypt_data.size());
CdmDecryptionParameters decryption_parameters(
&data->key_id, &data->encrypt_data.front(), data->encrypt_data.size(),
&data->iv, data->block_offset, &decrypt_buffer[0]);
decryption_parameters.is_encrypted = data->is_encrypted;
decryption_parameters.is_secure = data->is_secure;
decryption_parameters.subsample_flags = data->subsample_flags;
EXPECT_EQ(NO_ERROR, decryptor_.Decrypt(session_id_, data->validate_key_id,
decryption_parameters));
EXPECT_TRUE(std::equal(data->decrypt_data.begin(), data->decrypt_data.end(),
decrypt_buffer.begin()));
decryptor_.CloseSession(session_id_);
}
CdmUsageInfo usage_info;
EXPECT_EQ(
KEY_MESSAGE,
decryptor_.GetUsageInfo(app_id_empty, kDefaultCdmIdentifier,
&usage_info));
EXPECT_TRUE(usage_info.size() > 0);
EXPECT_EQ(
KEY_MESSAGE,
decryptor_.GetUsageInfo(app_id_not_empty, kDefaultCdmIdentifier,
&usage_info));
EXPECT_TRUE(usage_info.size() > 0);
// Read in usage info file
std::string path;
EXPECT_TRUE(Properties::GetDeviceFilesBasePath(security_level, &path));
std::string usage_info_not_empty_app_id_file_name =
path + DeviceFiles::GetUsageInfoFileName(app_id_not_empty);
ssize_t file_size =
file_system.FileSize(usage_info_not_empty_app_id_file_name);
EXPECT_LT(4, file_size);
File* file =
file_system.Open(usage_info_not_empty_app_id_file_name,
FileSystem::kReadOnly);
EXPECT_TRUE(NULL != file);
std::string file_data;
file_data.resize(file_size);
ssize_t bytes = file->Read(&file_data[0], file_data.size());
EXPECT_EQ(file_size, bytes);
file->Close();
// Corrupt the hash of the usage info file with non-empty app id and write
// it back out
memset(&file_data[0]+bytes-4, 0, 4);
file = file_system.Open(usage_info_not_empty_app_id_file_name,
FileSystem::kCreate | FileSystem::kTruncate);
EXPECT_TRUE(NULL != file);
bytes = file->Write(file_data.data(), file_data.size());
EXPECT_EQ(file_size, bytes);
file->Close();
EXPECT_EQ(
NO_ERROR,
decryptor_.RemoveAllUsageInfo(app_id_not_empty, kDefaultCdmIdentifier));
EXPECT_EQ(
NO_ERROR,
decryptor_.GetUsageInfo(app_id_not_empty, kDefaultCdmIdentifier,
&usage_info));
EXPECT_TRUE(usage_info.empty());
EXPECT_EQ(
KEY_MESSAGE,
decryptor_.GetUsageInfo(app_id_empty, kDefaultCdmIdentifier,
&usage_info));
EXPECT_TRUE(usage_info.size() > 0);
// Read in usage info file
std::string usage_info_empty_app_id_file_name =
path + DeviceFiles::GetUsageInfoFileName(app_id_empty);
file_size = file_system.FileSize(usage_info_empty_app_id_file_name);
EXPECT_LT(4, file_size);
file = file_system.Open(usage_info_empty_app_id_file_name,
FileSystem::kReadOnly);
EXPECT_TRUE(NULL != file);
file_data.resize(file_size);
bytes = file->Read(&file_data[0], file_data.size());
EXPECT_EQ(file_size, bytes);
file->Close();
// Corrupt the hash of the usage info file with empty app id and write it
// back out
memset(&file_data[0]+bytes-4, 0, 4);
file = file_system.Open(usage_info_empty_app_id_file_name,
FileSystem::kCreate | FileSystem::kTruncate);
EXPECT_TRUE(NULL != file);
bytes = file->Write(file_data.data(), file_data.size());
EXPECT_EQ(file_size, bytes);
file->Close();
EXPECT_EQ(
NO_ERROR,
decryptor_.RemoveAllUsageInfo(app_id_empty, kDefaultCdmIdentifier));
EXPECT_EQ(
NO_ERROR,
decryptor_.GetUsageInfo(app_id_not_empty, kDefaultCdmIdentifier,
&usage_info));
EXPECT_TRUE(usage_info.empty());
EXPECT_EQ(
NO_ERROR,
decryptor_.GetUsageInfo(app_id_empty, kDefaultCdmIdentifier,
&usage_info));
EXPECT_TRUE(usage_info.empty());
}
TEST_F(WvCdmRequestLicenseTest, GetSecureStopIdsTest) {
Unprovision();
@@ -3276,6 +3433,115 @@ TEST_F(WvCdmRequestLicenseTest, GetSecureStopIdsTest) {
EXPECT_TRUE(retrieved_secure_stop_ids.empty());
}
TEST_F(WvCdmRequestLicenseTest, UsageRecoveryTest) {
Unprovision();
std::string app_id_empty = "";
TestWvCdmClientPropertySet property_set;
Provision(kLevelDefault);
CdmSecurityLevel security_level = GetDefaultSecurityLevel();
FileSystem file_system;
DeviceFiles handle(&file_system);
EXPECT_TRUE(handle.Init(security_level));
std::vector<std::string> psts;
EXPECT_TRUE(handle.DeleteAllUsageInfoForApp(
DeviceFiles::GetUsageInfoFileName(""), &psts));
// Fetch a usage license
SubSampleInfo* data = kUsageLicenseTestVector1[0].sub_sample;
property_set.set_app_id(app_id_empty);
decryptor_.OpenSession(g_key_system, &property_set, kDefaultCdmIdentifier,
NULL, &session_id_);
GenerateKeyRequest(kUsageLicenseTestVector1[0].pssh, kLicenseTypeStreaming,
&property_set);
VerifyUsageKeyRequestResponse(g_license_server, g_client_auth);
std::vector<uint8_t> decrypt_buffer(data->encrypt_data.size());
CdmDecryptionParameters decryption_parameters(
&data->key_id, &data->encrypt_data.front(), data->encrypt_data.size(),
&data->iv, data->block_offset, &decrypt_buffer[0]);
decryption_parameters.is_encrypted = data->is_encrypted;
decryption_parameters.is_secure = data->is_secure;
decryption_parameters.subsample_flags = data->subsample_flags;
EXPECT_EQ(NO_ERROR, decryptor_.Decrypt(session_id_, data->validate_key_id,
decryption_parameters));
EXPECT_TRUE(std::equal(data->decrypt_data.begin(), data->decrypt_data.end(),
decrypt_buffer.begin()));
decryptor_.CloseSession(session_id_);
std::string path;
EXPECT_TRUE(Properties::GetDeviceFilesBasePath(security_level, &path));
std::string usage_info_file_name =
path + DeviceFiles::GetUsageInfoFileName(app_id_empty);
// Read in usage info file
ssize_t file_size = file_system.FileSize(usage_info_file_name);
EXPECT_LT(4, file_size);
File* file =
file_system.Open(usage_info_file_name, FileSystem::kReadOnly);
EXPECT_TRUE(NULL != file);
std::string file_data;
file_data.resize(file_size);
ssize_t bytes = file->Read(&file_data[0], file_data.size());
EXPECT_EQ(file_size, bytes);
file->Close();
// Corrupt the hash of the usage info file and write it back out
memset(&file_data[0]+bytes-4, 0, 4);
file = file_system.Open(usage_info_file_name,
FileSystem::kCreate | FileSystem::kTruncate);
EXPECT_TRUE(NULL != file);
bytes = file->Write(file_data.data(), file_data.size());
EXPECT_EQ(file_size, bytes);
file->Close();
// Fetch a second usage license, this should fail as the usage table is
// corrupt
decryptor_.OpenSession(g_key_system, &property_set, kDefaultCdmIdentifier,
NULL, &session_id_);
GenerateKeyRequest(kUsageLicenseTestVector1[1].pssh, kLicenseTypeStreaming,
&property_set);
std::string response;
VerifyKeyRequestResponse(wvcdm::STORE_USAGE_INFO_ERROR, g_license_server,
g_client_auth, true, &response);
decryptor_.CloseSession(session_id_);
// Fetch the second usage license and verify that it is usable
decryptor_.OpenSession(g_key_system, &property_set, kDefaultCdmIdentifier,
NULL, &session_id_);
GenerateKeyRequest(kUsageLicenseTestVector1[1].pssh, kLicenseTypeStreaming,
&property_set);
VerifyUsageKeyRequestResponse(g_license_server, g_client_auth);
data = kUsageLicenseTestVector1[1].sub_sample;
decrypt_buffer.resize(data->encrypt_data.size());
decryption_parameters.key_id = &data->key_id;
decryption_parameters.encrypt_buffer = &data->encrypt_data.front();
decryption_parameters.encrypt_length = data->encrypt_data.size();
decryption_parameters.iv = &data->iv;
decryption_parameters.block_offset = data->block_offset;
decryption_parameters.decrypt_buffer = &decrypt_buffer[0];
decryption_parameters.decrypt_buffer_length = data->encrypt_data.size();
decryption_parameters.is_encrypted = data->is_encrypted;
decryption_parameters.is_secure = data->is_secure;
decryption_parameters.subsample_flags = data->subsample_flags;
EXPECT_EQ(NO_ERROR, decryptor_.Decrypt(session_id_, data->validate_key_id,
decryption_parameters));
EXPECT_TRUE(std::equal(data->decrypt_data.begin(), data->decrypt_data.end(),
decrypt_buffer.begin()));
decryptor_.CloseSession(session_id_);
}
TEST_F(WvCdmRequestLicenseTest, UsageRemoveSecureStopTest) {
Unprovision();

View File

@@ -231,7 +231,6 @@ enum {
kUsageStoreLicenseFailed = ERROR_DRM_VENDOR_MIN + 241,
kUsageStoreUsageInfoFailed = ERROR_DRM_VENDOR_MIN + 242,
kUsageInvalidLoadEntry = ERROR_DRM_VENDOR_MIN + 243,
kRemoveAllUsageInfoError4 = ERROR_DRM_VENDOR_MIN + 244,
kRemoveAllUsageInfoError5 = ERROR_DRM_VENDOR_MIN + 245,
kReleaseUsageInfoFailed = ERROR_DRM_VENDOR_MIN + 246,
kIncorrectUsageSupportType1 = ERROR_DRM_VENDOR_MIN + 247,

View File

@@ -447,8 +447,6 @@ static android::status_t mapCdmResponseType(wvcdm::CdmResponseType res) {
return kIncorrectUsageSupportType1;
case wvcdm::INCORRECT_USAGE_SUPPORT_TYPE_2:
return kIncorrectUsageSupportType2;
case wvcdm::REMOVE_ALL_USAGE_INFO_ERROR_4:
return kRemoveAllUsageInfoError4;
case wvcdm::REMOVE_ALL_USAGE_INFO_ERROR_5:
return kRemoveAllUsageInfoError5;
case wvcdm::NO_USAGE_ENTRIES:

View File

@@ -264,7 +264,6 @@ static Status mapCdmResponseType(wvcdm::CdmResponseType res) {
case wvcdm::USAGE_STORE_LICENSE_FAILED:
case wvcdm::USAGE_STORE_USAGE_INFO_FAILED:
case wvcdm::USAGE_INVALID_LOAD_ENTRY:
case wvcdm::REMOVE_ALL_USAGE_INFO_ERROR_4:
case wvcdm::REMOVE_ALL_USAGE_INFO_ERROR_5:
case wvcdm::REMOVE_ALL_USAGE_INFO_ERROR_6:
case wvcdm::REMOVE_ALL_USAGE_INFO_ERROR_7: