diff --git a/libwvdrmengine/cdm/core/include/wv_cdm_types.h b/libwvdrmengine/cdm/core/include/wv_cdm_types.h index 5685878e..ec2f78c4 100644 --- a/libwvdrmengine/cdm/core/include/wv_cdm_types.h +++ b/libwvdrmengine/cdm/core/include/wv_cdm_types.h @@ -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, diff --git a/libwvdrmengine/cdm/core/src/cdm_engine.cpp b/libwvdrmengine/cdm/core/src/cdm_engine.cpp index 5929e6d4..034729af 100644 --- a/libwvdrmengine/cdm/core/src/cdm_engine.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_engine.cpp @@ -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 provider_session_tokens; if (!handle.DeleteAllUsageInfoForApp( diff --git a/libwvdrmengine/cdm/core/src/cdm_session.cpp b/libwvdrmengine/cdm/core/src/cdm_session.cpp index 5cb77013..ffac81bc 100644 --- a/libwvdrmengine/cdm/core/src/cdm_session.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_session.cpp @@ -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 provider_session_tokens; + file_handle_->DeleteAllUsageInfoForApp( + DeviceFiles::GetUsageInfoFileName(app_id), + &provider_session_tokens); + return STORE_USAGE_INFO_ERROR; } return NO_ERROR; diff --git a/libwvdrmengine/cdm/core/test/test_printers.cpp b/libwvdrmengine/cdm/core/test/test_printers.cpp index 7da8bbf5..863f3425 100644 --- a/libwvdrmengine/cdm/core/test/test_printers.cpp +++ b/libwvdrmengine/cdm/core/test/test_printers.cpp @@ -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; diff --git a/libwvdrmengine/cdm/test/request_license_test.cpp b/libwvdrmengine/cdm/test/request_license_test.cpp index 9af2eea2..3aa74ab4 100644 --- a/libwvdrmengine/cdm/test/request_license_test.cpp +++ b/libwvdrmengine/cdm/test/request_license_test.cpp @@ -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 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 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 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 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(); diff --git a/libwvdrmengine/include/WVErrors.h b/libwvdrmengine/include/WVErrors.h index 0e373979..631e4d0a 100644 --- a/libwvdrmengine/include/WVErrors.h +++ b/libwvdrmengine/include/WVErrors.h @@ -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, diff --git a/libwvdrmengine/include/mapErrors-inl.h b/libwvdrmengine/include/mapErrors-inl.h index e9d82b20..b4aa175c 100644 --- a/libwvdrmengine/include/mapErrors-inl.h +++ b/libwvdrmengine/include/mapErrors-inl.h @@ -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: diff --git a/libwvdrmengine/include_hidl/mapErrors-inl.h b/libwvdrmengine/include_hidl/mapErrors-inl.h index e64601c7..81b571fc 100644 --- a/libwvdrmengine/include_hidl/mapErrors-inl.h +++ b/libwvdrmengine/include_hidl/mapErrors-inl.h @@ -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: