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