From 4ba82e458595b303e59d721549808667be82aafe Mon Sep 17 00:00:00 2001 From: Rahul Frias Date: Fri, 9 Mar 2018 00:04:30 -0800 Subject: [PATCH] Secure stop API related changes [ Merge of http://go/wvgerrit/44921 ] * Added the ability to remove a single usage information record. * Added a method to retrieve all secure stop Ids. Bug: 69674645 Test: WV unit, integration tests Change-Id: I04ac8224b4bdda69541e61ff1103af3836138228 --- libwvdrmengine/cdm/core/include/cdm_engine.h | 14 +- .../cdm/core/include/device_files.h | 10 +- libwvdrmengine/cdm/core/src/cdm_engine.cpp | 97 ++++- libwvdrmengine/cdm/core/src/cdm_session.cpp | 20 +- libwvdrmengine/cdm/core/src/device_files.cpp | 25 +- .../cdm/core/test/device_files_unittest.cpp | 174 +++++++++ .../include/wv_content_decryption_module.h | 8 + .../cdm/src/wv_content_decryption_module.cpp | 31 ++ .../cdm/test/request_license_test.cpp | 349 +++++++++++++++++- 9 files changed, 700 insertions(+), 28 deletions(-) diff --git a/libwvdrmengine/cdm/core/include/cdm_engine.h b/libwvdrmengine/cdm/core/include/cdm_engine.h index c1d485ba..754960e4 100644 --- a/libwvdrmengine/cdm/core/include/cdm_engine.h +++ b/libwvdrmengine/cdm/core/include/cdm_engine.h @@ -188,11 +188,13 @@ class CdmEngine { virtual CdmResponseType ListStoredLicenses( CdmSecurityLevel security_level, std::vector* key_set_ids); - // Return the list of key_set_ids stored as usage records on the - // current (origin-specific) file system. - virtual CdmResponseType ListUsageRecords( + // Return the list of IDs associated with usage records for the + // current (origin-specific) file system. At least one parameter + // |ksids| or |provider_session_tokens| needs to be supplied. + virtual CdmResponseType ListUsageIds( const std::string& app_id, CdmSecurityLevel security_level, - std::vector* ksids); + std::vector* ksids, + std::vector* provider_session_tokens); // Delete the usage record for the given key_set_id. This removes the // usage record in the file system and the OEMCrypto usage record. @@ -220,6 +222,10 @@ class CdmEngine { // security levels. virtual CdmResponseType RemoveAllUsageInfo(const std::string& app_id); + virtual CdmResponseType RemoveUsageInfo( + const std::string& app_id, + const CdmSecureStopId& secure_stop_id); + virtual CdmResponseType ReleaseUsageInfo( const CdmUsageInfoReleaseMessage& message); virtual CdmResponseType LoadUsageSession(const CdmKeySetId& key_set_id, diff --git a/libwvdrmengine/cdm/core/include/device_files.h b/libwvdrmengine/cdm/core/include/device_files.h index 13aad015..a08a7c2b 100644 --- a/libwvdrmengine/cdm/core/include/device_files.h +++ b/libwvdrmengine/cdm/core/include/device_files.h @@ -105,9 +105,13 @@ class DeviceFiles { const CdmUsageEntry& usage_entry, uint32_t usage_entry_number); - // Extract KSIDs from usage information on the file system. - virtual bool ListUsageRecords(const std::string& app_id, - std::vector* ksids); + // Retrieve usage identifying information stored on the file system. + // The caller needs to specify at least one of |ksids| or + // |provider_session_tokens| + virtual bool ListUsageIds( + const std::string& app_id, + std::vector* ksids, + std::vector* provider_session_tokens); // Get the provider session token for the given key_set_id. virtual bool GetProviderSessionToken(const std::string& app_id, diff --git a/libwvdrmengine/cdm/core/src/cdm_engine.cpp b/libwvdrmengine/cdm/core/src/cdm_engine.cpp index 5a214b0a..8027f0bb 100644 --- a/libwvdrmengine/cdm/core/src/cdm_engine.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_engine.cpp @@ -929,20 +929,22 @@ CdmResponseType CdmEngine::ListStoredLicenses( return NO_ERROR; } -CdmResponseType CdmEngine::ListUsageRecords(const std::string& app_id, - CdmSecurityLevel security_level, - std::vector* ksids) { +CdmResponseType CdmEngine::ListUsageIds( + const std::string& app_id, + CdmSecurityLevel security_level, + std::vector* ksids, + std::vector* provider_session_tokens) { DeviceFiles handle(file_system_); - if (!ksids) { - LOGE("CdmEngine::ListUsageRecords: no response destination"); + if (!ksids && !provider_session_tokens) { + LOGE("CdmEngine::ListUsageIds: no response destination"); return INVALID_PARAMETERS_ENG_23; } if (!handle.Init(security_level)) { - LOGE("CdmEngine::ListUsageRecords: unable to initialize device files"); + LOGE("CdmEngine::ListUsageIds: unable to initialize device files"); return LIST_USAGE_ERROR_1; } - if (!handle.ListUsageRecords(app_id, ksids)) { - LOGE("CdmEngine::ListUsageRecords: ListUsageRecords call failed"); + if (!handle.ListUsageIds(app_id, ksids, provider_session_tokens)) { + LOGE("CdmEngine::ListUsageIds: ListUsageIds call failed"); return LIST_USAGE_ERROR_2; } return NO_ERROR; @@ -1272,6 +1274,85 @@ CdmResponseType CdmEngine::RemoveAllUsageInfo(const std::string& app_id) { return status; } +CdmResponseType CdmEngine::RemoveUsageInfo( + const std::string& app_id, + const CdmSecureStopId& provider_session_token) { + if (NULL == usage_property_set_.get()) { + usage_property_set_.reset(new UsagePropertySet()); + } + usage_property_set_->set_app_id(app_id); + + CdmResponseType status = NO_ERROR; + for (int j = kSecurityLevelL1; j < kSecurityLevelUnknown; ++j) { + DeviceFiles handle(file_system_); + if (handle.Init(static_cast(j))) { + SecurityLevel security_level = + static_cast(j) == kSecurityLevelL3 + ? kLevel3 + : kLevelDefault; + usage_property_set_->set_security_level(security_level); + usage_session_.reset(new CdmSession(file_system_, metrics_.AddSession())); + usage_session_->Init(usage_property_set_.get()); + + std::vector usage_data; + CdmKeyMessage license_request; + CdmKeyResponse license_response; + CdmUsageEntry usage_entry; + uint32_t usage_entry_number; + + if (!handle.RetrieveUsageInfo( + DeviceFiles::GetUsageInfoFileName(app_id), provider_session_token, + &license_request, &license_response, &usage_entry, + &usage_entry_number)) { + // Try other security level + continue; + } + + switch (usage_session_->get_usage_support_type()) { + case kUsageEntrySupport: { + status = usage_session_->DeleteUsageEntry( + usage_data[0].usage_entry_number); + + if (!handle.DeleteUsageInfo( + DeviceFiles::GetUsageInfoFileName(app_id), + provider_session_token)) { + status = REMOVE_USAGE_INFO_ERROR_1; + } + usage_session_.reset(NULL); + return status; + } + case kUsageTableSupport: { + std::vector provider_session_tokens; + handle.DeleteUsageInfo( + DeviceFiles::GetUsageInfoFileName(app_id), + provider_session_token); + scoped_ptr crypto_session( + new CryptoSession(metrics_.GetCryptoMetrics())); + status = crypto_session->Open( + static_cast(j) == kSecurityLevelL3 + ? kLevel3 : kLevelDefault); + if (status == NO_ERROR) { + crypto_session->UpdateUsageInformation(); + status = + crypto_session->DeleteUsageInformation(provider_session_token); + crypto_session->UpdateUsageInformation(); + } + return status; + } + default: + // Ignore + break; + } + } else { + LOGE("CdmEngine::RemoveUsageInfo: failed to initialize L%d devicefiles", + j); + status = REMOVE_USAGE_INFO_ERROR_2; + } + } + usage_session_.reset(NULL); + return REMOVE_USAGE_INFO_ERROR_3; +} + CdmResponseType CdmEngine::ReleaseUsageInfo( const CdmUsageInfoReleaseMessage& message) { if (NULL == usage_session_.get()) { diff --git a/libwvdrmengine/cdm/core/src/cdm_session.cpp b/libwvdrmengine/cdm/core/src/cdm_session.cpp index bc7876b0..3de40213 100644 --- a/libwvdrmengine/cdm/core/src/cdm_session.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_session.cpp @@ -868,10 +868,22 @@ void CdmSession::GetApplicationId(std::string* app_id) { CdmResponseType CdmSession::DeleteMultipleUsageInformation( const std::vector& provider_session_tokens) { - CdmResponseType sts = crypto_session_->DeleteMultipleUsageInformation( - provider_session_tokens); - crypto_metrics_->crypto_session_delete_multiple_usage_information_ - .Increment(sts); + CdmUsageSupportType usage_support_type; + CdmResponseType sts = + crypto_session_->GetUsageSupportType(&usage_support_type); + if (sts == NO_ERROR && usage_support_type == kUsageTableSupport) { + for (size_t i = 0; i < provider_session_tokens.size(); ++i) { + crypto_session_->DeactivateUsageInformation(provider_session_tokens[i]); + UpdateUsageTableInformation(); + } + } + + if (sts == NO_ERROR) { + sts = crypto_session_->DeleteMultipleUsageInformation( + provider_session_tokens); + crypto_metrics_->crypto_session_delete_multiple_usage_information_ + .Increment(sts); + } return sts; } diff --git a/libwvdrmengine/cdm/core/src/device_files.cpp b/libwvdrmengine/cdm/core/src/device_files.cpp index 4db8d3de..8ae95fc5 100644 --- a/libwvdrmengine/cdm/core/src/device_files.cpp +++ b/libwvdrmengine/cdm/core/src/device_files.cpp @@ -464,22 +464,25 @@ bool DeviceFiles::StoreUsageInfo(const std::string& provider_session_token, return StoreFileWithHash(usage_info_file_name, serialized_file); } -bool DeviceFiles::ListUsageRecords(const std::string& app_id, - std::vector* ksids) { +bool DeviceFiles::ListUsageIds( + const std::string& app_id, + std::vector* ksids, + std::vector* provider_session_tokens) { if (!initialized_) { - LOGW("DeviceFiles::ListUsageRecords: not initialized"); + LOGW("DeviceFiles::ListUsageIds: not initialized"); return false; } - if (ksids == NULL) { - LOGW("DeviceFiles::ListUsageRecords: return parameter not provided"); + if (ksids == NULL && provider_session_tokens == NULL) { + LOGW("DeviceFiles::ListUsageIds: ksids or pst parameter not provided"); return false; } // Empty or non-existent file == no usage records. std::string file_name = GetUsageInfoFileName(app_id); if (!FileExists(file_name) || GetFileSize(file_name) == 0) { - ksids->clear(); + if (ksids != NULL) ksids->clear(); + if (provider_session_tokens != NULL) provider_session_tokens->clear(); return true; } @@ -489,13 +492,19 @@ bool DeviceFiles::ListUsageRecords(const std::string& app_id, return false; } - ksids->clear(); + if (ksids != NULL) ksids->clear(); + if (provider_session_tokens != NULL) provider_session_tokens->clear(); size_t num_records = file.usage_info().sessions_size(); for (size_t i = 0; i < num_records; ++i) { - if (!file.usage_info().sessions(i).key_set_id().empty()) { + if ((ksids != NULL) && + !file.usage_info().sessions(i).key_set_id().empty()) { ksids->push_back(file.usage_info().sessions(i).key_set_id()); } + if ((provider_session_tokens != NULL) && + !file.usage_info().sessions(i).token().empty()) { + provider_session_tokens->push_back(file.usage_info().sessions(i).token()); + } } return true; } diff --git a/libwvdrmengine/cdm/core/test/device_files_unittest.cpp b/libwvdrmengine/cdm/core/test/device_files_unittest.cpp index 1fe4632b..87c1f8e8 100644 --- a/libwvdrmengine/cdm/core/test/device_files_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/device_files_unittest.cpp @@ -2629,6 +2629,73 @@ TEST_F(DeviceFilesUsageInfoTest, ListNullParam) { EXPECT_FALSE(device_files.ListUsageInfoFiles(NULL)); } +TEST_F(DeviceFilesUsageInfoTest, ListIdsNull) { + MockFileSystem file_system; + MockFile file; + + std::string app_id = kUsageInfoTestData[0].app_id; + + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + EXPECT_FALSE(device_files.ListUsageIds(app_id, NULL, NULL)); +} + +TEST_F(DeviceFilesUsageInfoTest, ListUsageIds) { + MockFileSystem file_system; + MockFile file; + + int index = 8; + std::string app_id = kUsageInfoTestData[index].app_id; + + std::string file_name = DeviceFiles::GetUsageInfoFileName(app_id); + std::string path = device_base_path_ + file_name; + std::string file_data = (index < 0) ? kEmptyUsageInfoFileData + : kUsageInfoTestData[index].file_data; + if (index >= 0) { + EXPECT_CALL(file_system, Exists(StrEq(path))) + .Times(2) + .WillRepeatedly(Return(true)); + EXPECT_CALL(file_system, FileSize(StrEq(path))) + .Times(2) + .WillRepeatedly(Return(kUsageInfoTestData[index].file_data.size())); + EXPECT_CALL(file_system, Open(StrEq(path), _)).WillOnce(Return(&file)); + EXPECT_CALL(file, + Read(NotNull(), Eq(kUsageInfoTestData[index].file_data.size()))) + .WillOnce(DoAll(SetArrayArgument<0>(file_data.begin(), file_data.end()), + Return(file_data.size()))); + EXPECT_CALL(file, Close()); + } + else { + EXPECT_CALL(file_system, Exists(StrEq(path))) + .WillOnce(Return(false)); + } + + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + + std::vector key_set_ids; + std::vector provider_session_tokens; + EXPECT_TRUE(device_files.ListUsageIds( + app_id, &key_set_ids, &provider_session_tokens)); + + EXPECT_EQ(key_set_ids.size(), provider_session_tokens.size()); + if (index >= 0) { + for (size_t i = 0; i < provider_session_tokens.size(); ++i) { + bool found = false; + for (int j = 0; !found && j <= index; ++j) { + if (app_id == kUsageInfoTestData[j].app_id && + kUsageInfoTestData[j].usage_data.provider_session_token == + provider_session_tokens[i] && + kUsageInfoTestData[j].usage_data.key_set_id == + key_set_ids[i]) { + found = true; + } + } + EXPECT_TRUE(found); + } + } +} + TEST_P(DeviceFilesUsageInfoListTest, UsageInfoList) { MockFileSystem file_system; MockFile file; @@ -2767,6 +2834,113 @@ TEST_P(DeviceFilesUsageInfoTest, Retrieve) { } } +TEST_P(DeviceFilesUsageInfoTest, ListKeySetIds) { + MockFileSystem file_system; + MockFile file; + + int index = GetParam(); + + std::string app_id; + if (index >= 0) app_id = kUsageInfoTestData[index].app_id; + + std::string file_name = DeviceFiles::GetUsageInfoFileName(app_id); + std::string path = device_base_path_ + file_name; + std::string file_data = (index < 0) ? kEmptyUsageInfoFileData + : kUsageInfoTestData[index].file_data; + if (index >= 0) { + EXPECT_CALL(file_system, Exists(StrEq(path))) + .Times(2) + .WillRepeatedly(Return(true)); + EXPECT_CALL(file_system, FileSize(StrEq(path))) + .Times(2) + .WillRepeatedly(Return(kUsageInfoTestData[index].file_data.size())); + EXPECT_CALL(file_system, Open(StrEq(path), _)).WillOnce(Return(&file)); + EXPECT_CALL(file, + Read(NotNull(), Eq(kUsageInfoTestData[index].file_data.size()))) + .WillOnce(DoAll(SetArrayArgument<0>(file_data.begin(), file_data.end()), + Return(file_data.size()))); + EXPECT_CALL(file, Close()); + } + else { + EXPECT_CALL(file_system, Exists(StrEq(path))) + .WillOnce(Return(false)); + } + + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + + std::vector key_set_ids; + EXPECT_TRUE(device_files.ListUsageIds(app_id, &key_set_ids, NULL)); + + if (index >= 0) { + for (size_t i = 0; i < key_set_ids.size(); ++i) { + bool found = false; + for (int j = 0; !found && j <= index; ++j) { + if (app_id == kUsageInfoTestData[j].app_id && + kUsageInfoTestData[j].usage_data.key_set_id == + key_set_ids[i]) { + found = true; + } + } + EXPECT_TRUE(found); + } + } +} + +TEST_P(DeviceFilesUsageInfoTest, ListProviderSessionTokenIds) { + MockFileSystem file_system; + MockFile file; + + int index = GetParam(); + + std::string app_id; + if (index >= 0) app_id = kUsageInfoTestData[index].app_id; + + std::string file_name = DeviceFiles::GetUsageInfoFileName(app_id); + std::string path = device_base_path_ + file_name; + std::string file_data = (index < 0) ? kEmptyUsageInfoFileData + : kUsageInfoTestData[index].file_data; + if (index >= 0) { + EXPECT_CALL(file_system, Exists(StrEq(path))) + .Times(2) + .WillRepeatedly(Return(true)); + EXPECT_CALL(file_system, FileSize(StrEq(path))) + .Times(2) + .WillRepeatedly(Return(kUsageInfoTestData[index].file_data.size())); + EXPECT_CALL(file_system, Open(StrEq(path), _)).WillOnce(Return(&file)); + EXPECT_CALL(file, + Read(NotNull(), Eq(kUsageInfoTestData[index].file_data.size()))) + .WillOnce(DoAll(SetArrayArgument<0>(file_data.begin(), file_data.end()), + Return(file_data.size()))); + EXPECT_CALL(file, Close()); + } + else { + EXPECT_CALL(file_system, Exists(StrEq(path))) + .WillOnce(Return(false)); + } + + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + + std::vector provider_session_tokens; + EXPECT_TRUE( + device_files.ListUsageIds(app_id, NULL, &provider_session_tokens)); + + if (index >= 0) { + for (size_t i = 0; i < provider_session_tokens.size(); ++i) { + bool found = false; + for (int j = 0; !found && j <= index; ++j) { + if (app_id == kUsageInfoTestData[j].app_id && + kUsageInfoTestData[j].usage_data.provider_session_token == + provider_session_tokens[i]) { + found = true; + } + } + EXPECT_TRUE(found); + } + } +} + TEST_P(DeviceFilesUsageInfoTest, RetrieveByProviderSessionToken) { MockFileSystem file_system; MockFile file; diff --git a/libwvdrmengine/cdm/include/wv_content_decryption_module.h b/libwvdrmengine/cdm/include/wv_content_decryption_module.h index 4c5fe843..52957731 100644 --- a/libwvdrmengine/cdm/include/wv_content_decryption_module.h +++ b/libwvdrmengine/cdm/include/wv_content_decryption_module.h @@ -107,10 +107,18 @@ class WvContentDecryptionModule : public android::RefBase, public TimerHandler { CdmUsageInfo* usage_info); virtual CdmResponseType RemoveAllUsageInfo(const std::string& app_id, const CdmIdentifier& identifier); + virtual CdmResponseType RemoveUsageInfo( + const std::string& app_id, + const CdmIdentifier& identifier, + const CdmSecureStopId& secure_stop_id); virtual CdmResponseType ReleaseUsageInfo( const CdmUsageInfoReleaseMessage& message, const CdmIdentifier& identifier); + virtual CdmResponseType GetSecureStopIds(const std::string& app_id, + const CdmIdentifier& identifier, + std::vector* ssids); + // Accept encrypted buffer and decrypt data. // Decryption parameters that need to be specified are // is_encrypted, is_secure, key_id, encrypt_buffer, encrypt_length, diff --git a/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp b/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp index 4e5d18a0..666fe7f2 100644 --- a/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp +++ b/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp @@ -305,6 +305,16 @@ CdmResponseType WvContentDecryptionModule::RemoveAllUsageInfo( return sts; } +CdmResponseType WvContentDecryptionModule::RemoveUsageInfo( + const std::string& app_id, + const CdmIdentifier& identifier, + const CdmSecureStopId& secure_stop_id) { + CdmEngine* cdm_engine = EnsureCdmForIdentifier(identifier); + CdmResponseType sts = cdm_engine->RemoveUsageInfo(app_id, secure_stop_id); + cdm_engine->GetMetrics()->cdm_engine_remove_usage_info_.Increment(sts); + return sts; +} + CdmResponseType WvContentDecryptionModule::ReleaseUsageInfo( const CdmUsageInfoReleaseMessage& message, const CdmIdentifier& identifier) { @@ -314,6 +324,27 @@ CdmResponseType WvContentDecryptionModule::ReleaseUsageInfo( return sts; } +CdmResponseType WvContentDecryptionModule::GetSecureStopIds( + const std::string& app_id, + const CdmIdentifier& identifier, + std::vector* ssids) { + if (ssids == NULL) { + LOGE("WvContentDecryptionModule::Decrypt: ssid destination not provided"); + return PARAMETER_NULL; + } + + CdmEngine* cdm_engine = EnsureCdmForIdentifier(identifier); + CdmResponseType sts = cdm_engine->ListUsageIds(app_id, kSecurityLevelL1, + NULL, ssids); + std::vector secure_stop_ids; + CdmResponseType sts_l3 = cdm_engine->ListUsageIds(app_id, kSecurityLevelL3, + NULL, &secure_stop_ids); + ssids->insert(ssids->end(), secure_stop_ids.begin(), secure_stop_ids.end()); + if (sts_l3 != NO_ERROR) sts = sts_l3; + cdm_engine->GetMetrics()->cdm_engine_get_secure_stop_ids_.Increment(sts); + return sts; +} + CdmResponseType WvContentDecryptionModule::Decrypt( const CdmSessionId& session_id, bool validate_key_id, diff --git a/libwvdrmengine/cdm/test/request_license_test.cpp b/libwvdrmengine/cdm/test/request_license_test.cpp index ddae4bdc..25549269 100644 --- a/libwvdrmengine/cdm/test/request_license_test.cpp +++ b/libwvdrmengine/cdm/test/request_license_test.cpp @@ -37,6 +37,7 @@ using ::testing::IsEmpty; using ::testing::Not; using ::testing::Pair; using ::testing::StrictMock; +using ::testing::UnorderedElementsAreArray; namespace { @@ -418,6 +419,55 @@ UsageInfoSubSampleInfo usage_info_sub_sample_info[] = { {&usage_info_sub_samples_icp[0], 5, wvcdm::kLevel3, ""}, {&usage_info_sub_samples_icp[0], 3, wvcdm::kLevel3, "other app id"}}; +struct UsageLicenseAndSubSampleInfo { + std::string pssh; + SubSampleInfo* sub_sample; + std::string provider_session_token; +}; + +std::string kPsshStreamingClip3 = wvcdm::a2bs_hex( + "000000427073736800000000" // blob size and pssh + "EDEF8BA979D64ACEA3C827DCD51D21ED00000022" // Widevine system id + "08011a0d7769646576696e655f74657374220f73" // pssh data + "747265616d696e675f636c697033"); +std::string kPsshStreamingClip4 = wvcdm::a2bs_hex( + "000000427073736800000000" // blob size and pssh + "EDEF8BA979D64ACEA3C827DCD51D21ED00000022" // Widevine system id + "08011a0d7769646576696e655f74657374220f73" // pssh data + "747265616d696e675f636c697034"); +std::string kPsshStreamingClip5 = wvcdm::a2bs_hex( + "000000427073736800000000" // blob size and pssh + "EDEF8BA979D64ACEA3C827DCD51D21ED00000022" // Widevine system id + "08011a0d7769646576696e655f74657374220f73" // pssh data + "747265616d696e675f636c697035"); +std::string kPsshStreamingClip7 = wvcdm::a2bs_hex( + "000000427073736800000000" // blob size and pssh + "EDEF8BA979D64ACEA3C827DCD51D21ED00000022" // Widevine system id + "08011a0d7769646576696e655f74657374220f73" // pssh data + "747265616d696e675f636c697037"); + +std::string kProviderSessionTokenStreamingClip3 = wvcdm::a2bs_hex( + "4851305A4A4156485A554936444E4931"); +std::string kProviderSessionTokenStreamingClip4 = wvcdm::a2bs_hex( + "4942524F4355544E5557553145463243"); +std::string kProviderSessionTokenStreamingClip7 = wvcdm::a2bs_hex( + "44434C53524F4E30394C4E5535544B4C"); + +UsageLicenseAndSubSampleInfo kUsageLicenseTestVector1[] = { + { kPsshStreamingClip3, &usage_info_sub_samples_icp[0], + kProviderSessionTokenStreamingClip3 }, + { kPsshStreamingClip4, &usage_info_sub_samples_icp[1], + kProviderSessionTokenStreamingClip4 }, +}; + +UsageLicenseAndSubSampleInfo kUsageLicenseTestVector2[] = { + { kPsshStreamingClip7, &usage_info_sub_samples_icp[4], + kProviderSessionTokenStreamingClip7 }, + // TODO(rfrias): Add another streaming usage license. Streaming + // clip 5 has includes a randomly generated PST, while + // streaming clip 6 does not include a PST. +}; + struct RenewWithClientIdTestConfiguration { bool always_include_client_id; bool specify_app_parameters; @@ -1273,6 +1323,18 @@ class WvCdmRequestLicenseTest : public WvCdmTestBase { CdmAppParameterMap& app_parameters, CdmLicenseType license_type, CdmClientPropertySet* property_set) { + GenerateKeyRequest(expected_response, init_data_type, init_data, + app_parameters, license_type, kDefaultCdmIdentifier, + property_set); + } + + void GenerateKeyRequest(CdmResponseType expected_response, + const std::string& init_data_type, + const std::string& init_data, + CdmAppParameterMap& app_parameters, + CdmLicenseType license_type, + const CdmIdentifier& cdm_identifier, + CdmClientPropertySet* property_set) { CdmKeyRequest key_request; std::string key_set_id; license_type_ = license_type; @@ -1280,7 +1342,7 @@ class WvCdmRequestLicenseTest : public WvCdmTestBase { decryptor_.GenerateKeyRequest( session_id_, key_set_id, init_data_type, init_data, license_type, app_parameters, property_set, - kDefaultCdmIdentifier, &key_request)); + cdm_identifier, &key_request)); key_msg_ = key_request.message; EXPECT_EQ(0u, key_request.url.size()); } @@ -3054,6 +3116,291 @@ TEST_F(WvCdmRequestLicenseTest, UsageRemoveAllTest) { EXPECT_TRUE(usage_info.empty()); } +TEST_F(WvCdmRequestLicenseTest, GetSecureStopIdsTest) { + 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)); + + std::vector retrieved_secure_stop_ids; + EXPECT_EQ( + NO_ERROR, + decryptor_.GetSecureStopIds(app_id_empty, kDefaultCdmIdentifier, + &retrieved_secure_stop_ids)); + EXPECT_TRUE(retrieved_secure_stop_ids.empty()); + + // First fetch licenses for the default app + for (size_t i = 0; i < N_ELEM(kUsageLicenseTestVector1); ++i) { + SubSampleInfo* data = kUsageLicenseTestVector1[i].sub_sample; + + property_set.set_app_id(app_id_empty); + decryptor_.OpenSession(g_key_system, &property_set, kDefaultCdmIdentifier, + NULL, &session_id_); + GenerateKeyRequest(kUsageLicenseTestVector1[i].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_); + } + + // Provision the other identifier + Provision(kExampleIdentifier, kLevelDefault); + + // Verify that there are usage records for the default identifier but + // none yet for the non-default one + std::vector expected_provider_session_tokens; + for (size_t i = 0; i < N_ELEM(kUsageLicenseTestVector1); ++i) { + expected_provider_session_tokens.push_back( + kUsageLicenseTestVector1[i].provider_session_token); + } + + EXPECT_EQ( + NO_ERROR, + decryptor_.GetSecureStopIds(app_id_empty, kDefaultCdmIdentifier, + &retrieved_secure_stop_ids)); + EXPECT_EQ(N_ELEM(kUsageLicenseTestVector1), retrieved_secure_stop_ids.size()); + EXPECT_THAT(retrieved_secure_stop_ids, + UnorderedElementsAreArray(expected_provider_session_tokens)); + + EXPECT_EQ( + NO_ERROR, + decryptor_.GetSecureStopIds(kExampleIdentifier.app_package_name, + kDefaultCdmIdentifier, + &retrieved_secure_stop_ids)); + EXPECT_TRUE(retrieved_secure_stop_ids.empty()); + + // Now fetch licenses for the other identifier + for (size_t i = 0; i < N_ELEM(kUsageLicenseTestVector2); ++i) { + SubSampleInfo* data = kUsageLicenseTestVector2[i].sub_sample; + + property_set.set_app_id(kExampleIdentifier.app_package_name); + EXPECT_EQ(NO_ERROR, + decryptor_.OpenSession(g_key_system, &property_set, + kExampleIdentifier, NULL, + &session_id_)); + std::string init_data_type = "video/mp4"; + CdmAppParameterMap app_parameters; + GenerateKeyRequest(wvcdm::KEY_MESSAGE, init_data_type, + kUsageLicenseTestVector2[i].pssh, app_parameters, + kLicenseTypeStreaming, kExampleIdentifier, + &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_); + } + + // Verify that there are usage records for both the default and + // non-default identifier. + EXPECT_EQ( + NO_ERROR, + decryptor_.GetSecureStopIds(kDefaultCdmIdentifier.app_package_name, + kDefaultCdmIdentifier, + &retrieved_secure_stop_ids)); + EXPECT_EQ(N_ELEM(kUsageLicenseTestVector1), retrieved_secure_stop_ids.size()); + EXPECT_THAT(retrieved_secure_stop_ids, + UnorderedElementsAreArray(expected_provider_session_tokens)); + + retrieved_secure_stop_ids.clear(); + expected_provider_session_tokens.clear(); + + for (size_t i = 0; i < N_ELEM(kUsageLicenseTestVector2); ++i) { + expected_provider_session_tokens.push_back( + kUsageLicenseTestVector2[i].provider_session_token); + } + + EXPECT_EQ( + NO_ERROR, + decryptor_.GetSecureStopIds(kExampleIdentifier.app_package_name, + kExampleIdentifier, + &retrieved_secure_stop_ids)); + EXPECT_EQ(N_ELEM(kUsageLicenseTestVector2), retrieved_secure_stop_ids.size()); + EXPECT_THAT(retrieved_secure_stop_ids, + UnorderedElementsAreArray(expected_provider_session_tokens)); + + EXPECT_EQ( + NO_ERROR, + decryptor_.RemoveAllUsageInfo(kDefaultCdmIdentifier.app_package_name, + kDefaultCdmIdentifier)); + EXPECT_EQ( + NO_ERROR, + decryptor_.RemoveAllUsageInfo(kExampleIdentifier.app_package_name, + kExampleIdentifier)); + + EXPECT_EQ( + NO_ERROR, + decryptor_.GetSecureStopIds(kDefaultCdmIdentifier.app_package_name, + kDefaultCdmIdentifier, + &retrieved_secure_stop_ids)); + EXPECT_TRUE(retrieved_secure_stop_ids.empty()); + EXPECT_EQ( + NO_ERROR, + decryptor_.GetSecureStopIds(kExampleIdentifier.app_package_name, + kExampleIdentifier, + &retrieved_secure_stop_ids)); + EXPECT_TRUE(retrieved_secure_stop_ids.empty()); +} + +TEST_F(WvCdmRequestLicenseTest, UsageRemoveSecureStopTest) { + 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)); + + // First fetch licenses for the default app + for (size_t i = 0; i < N_ELEM(kUsageLicenseTestVector1); ++i) { + SubSampleInfo* data = kUsageLicenseTestVector1[i].sub_sample; + + property_set.set_app_id(app_id_empty); + decryptor_.OpenSession(g_key_system, &property_set, kDefaultCdmIdentifier, + NULL, &session_id_); + GenerateKeyRequest(kUsageLicenseTestVector1[i].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_); + } + + // Provision and fetch licenses for the other identifier + Provision(kExampleIdentifier, kLevelDefault); + + for (size_t i = 0; i < N_ELEM(kUsageLicenseTestVector2); ++i) { + SubSampleInfo* data = kUsageLicenseTestVector2[i].sub_sample; + + property_set.set_app_id(kExampleIdentifier.app_package_name); + EXPECT_EQ(NO_ERROR, + decryptor_.OpenSession(g_key_system, &property_set, + kExampleIdentifier, NULL, + &session_id_)); + std::string init_data_type = "video/mp4"; + CdmAppParameterMap app_parameters; + GenerateKeyRequest(wvcdm::KEY_MESSAGE, init_data_type, + kUsageLicenseTestVector2[i].pssh, app_parameters, + kLicenseTypeStreaming, kExampleIdentifier, + &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_); + } + + // Release usage records for both the default and non-default identifier. + std::vector secure_stop_ids; + EXPECT_EQ( + NO_ERROR, + decryptor_.GetSecureStopIds(kDefaultCdmIdentifier.app_package_name, + kDefaultCdmIdentifier, + &secure_stop_ids)); + EXPECT_EQ(N_ELEM(kUsageLicenseTestVector1), secure_stop_ids.size()); + + for (size_t i = 0; i < N_ELEM(kUsageLicenseTestVector1); ++i) { + EXPECT_EQ( + NO_ERROR, + decryptor_.RemoveUsageInfo(kDefaultCdmIdentifier.app_package_name, + kDefaultCdmIdentifier, + secure_stop_ids[i])); + } + + EXPECT_EQ( + NO_ERROR, + decryptor_.GetSecureStopIds(kDefaultCdmIdentifier.app_package_name, + kDefaultCdmIdentifier, + &secure_stop_ids)); + EXPECT_TRUE(secure_stop_ids.empty()); + + EXPECT_EQ( + NO_ERROR, + decryptor_.GetSecureStopIds(kExampleIdentifier.app_package_name, + kExampleIdentifier, + &secure_stop_ids)); + EXPECT_EQ(N_ELEM(kUsageLicenseTestVector2), secure_stop_ids.size()); + + for (size_t i = 0; i < N_ELEM(kUsageLicenseTestVector2); ++i) { + EXPECT_EQ( + NO_ERROR, + decryptor_.RemoveUsageInfo(kExampleIdentifier.app_package_name, + kExampleIdentifier, + secure_stop_ids[i])); + } + + EXPECT_EQ( + NO_ERROR, + decryptor_.GetSecureStopIds(kExampleIdentifier.app_package_name, + kExampleIdentifier, + &secure_stop_ids)); + EXPECT_TRUE(secure_stop_ids.empty()); +} + TEST_F(WvCdmRequestLicenseTest, QueryUnmodifiedSessionStatus) { // Test that the global value is returned when no properties are modifying it. std::string security_level;