From aa7ad630d7809e0dbe5858254251e8e35b3ba7b1 Mon Sep 17 00:00:00 2001 From: Rahul Frias Date: Thu, 13 Nov 2014 11:51:05 -0800 Subject: [PATCH] MediaDrm throws an exception when Secure Stops are requested Our recommendation to OEMs is that they support a table of at least 50 usage entries in OEMCrypto. If more usage entries are stored, the PSTs get added to the CDM but are LRU'ed out of the OEMCrypto usage table. When the CDM queries those usage entries, OEMCrypto will return a OEMCrypto_ERROR_INVALID_CONTEXT. Rather than return an error and have MediaDrm throw an exception, CDM should delete this PST and return the next usage entry, when queried. [ Merge of https://widevine-internal-review.googlesource.com/#/c/11457/ from Widevine cdm repo ] b/17994711 Change-Id: I00e3f93000096fb434d94333e22958de795a4bb5 --- libwvdrmengine/cdm/core/include/cdm_session.h | 3 +- .../cdm/core/include/device_files.h | 2 + libwvdrmengine/cdm/core/include/license.h | 4 +- libwvdrmengine/cdm/core/src/cdm_engine.cpp | 39 ++++++---- libwvdrmengine/cdm/core/src/cdm_session.cpp | 17 +++-- .../cdm/core/src/crypto_session.cpp | 14 +++- libwvdrmengine/cdm/core/src/license.cpp | 41 +++++----- .../cdm/test/cdm_extended_duration_test.cpp | 76 +++++++++++++++++++ .../cdm/test/request_license_test.cpp | 62 +++++++++++++++ 9 files changed, 211 insertions(+), 47 deletions(-) diff --git a/libwvdrmengine/cdm/core/include/cdm_session.h b/libwvdrmengine/cdm/core/include/cdm_session.h index e0527117..64ff516d 100644 --- a/libwvdrmengine/cdm/core/include/cdm_session.h +++ b/libwvdrmengine/cdm/core/include/cdm_session.h @@ -100,6 +100,8 @@ class CdmSession { is_usage_update_needed_ = false; } + bool DeleteLicense(); + private: // Internal constructor void Create(CdmLicense* license_parser, CryptoSession* crypto_session, @@ -112,7 +114,6 @@ class CdmSession { CdmResponseType StoreLicense(); bool StoreLicense(DeviceFiles::LicenseState state); - bool DeleteLicense(); // instance variables bool initialized_; diff --git a/libwvdrmengine/cdm/core/include/device_files.h b/libwvdrmengine/cdm/core/include/device_files.h index ac225651..a02e5404 100644 --- a/libwvdrmengine/cdm/core/include/device_files.h +++ b/libwvdrmengine/cdm/core/include/device_files.h @@ -108,7 +108,9 @@ class DeviceFiles { FRIEND_TEST(DeviceFilesUsageInfoTest, Store); FRIEND_TEST(WvCdmRequestLicenseTest, UnprovisionTest); FRIEND_TEST(WvCdmRequestLicenseTest, ForceL3Test); + FRIEND_TEST(WvCdmRequestLicenseTest, UsageInfoRetryTest); FRIEND_TEST(WvCdmUsageInfoTest, UsageInfo); + FRIEND_TEST(WvCdmExtendedDurationTest, UsageOverflowTest); #endif scoped_ptr file_; diff --git a/libwvdrmengine/cdm/core/include/license.h b/libwvdrmengine/cdm/core/include/license.h index ac4c502b..e88dd214 100644 --- a/libwvdrmengine/cdm/core/include/license.h +++ b/libwvdrmengine/cdm/core/include/license.h @@ -36,8 +36,8 @@ class CdmLicense { const CdmSessionId& session_id, CdmKeyMessage* signed_request, std::string* server_url); - virtual bool PrepareKeyUpdateRequest(bool is_renewal, CdmKeyMessage* signed_request, - std::string* server_url); + virtual CdmResponseType PrepareKeyUpdateRequest( + bool is_renewal, CdmKeyMessage* signed_request, std::string* server_url); virtual CdmResponseType HandleKeyResponse(const CdmKeyResponse& license_response); virtual CdmResponseType HandleKeyUpdateResponse( bool is_renewal, const CdmKeyResponse& license_response); diff --git a/libwvdrmengine/cdm/core/src/cdm_engine.cpp b/libwvdrmengine/cdm/core/src/cdm_engine.cpp index 3c91e2d1..905cb0c4 100644 --- a/libwvdrmengine/cdm/core/src/cdm_engine.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_engine.cpp @@ -654,16 +654,22 @@ CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id, CdmUsageInfo* usage_info) { // Return a random usage report from a random security level SecurityLevel security_level = ((rand() % 2) == 0) ? kLevelDefault : kLevel3; - CdmResponseType status = GetUsageInfo(app_id, security_level, usage_info); + CdmResponseType status = UNKNOWN_ERROR; + do { + status = GetUsageInfo(app_id, security_level, usage_info); + + if (KEY_MESSAGE == status && !usage_info->empty()) + return status; + } while (KEY_CANCELED == status); - if (KEY_MESSAGE == status && !usage_info->empty()) - return status; security_level = (kLevel3 == security_level) ? kLevelDefault : kLevel3; - status = GetUsageInfo(app_id, security_level, usage_info); - if (NEED_PROVISIONING == status) - return NO_ERROR; // Valid scenario that one of the security - // levels has not been provisioned + do { + status = GetUsageInfo(app_id, security_level, usage_info); + if (NEED_PROVISIONING == status) + return NO_ERROR; // Valid scenario that one of the security + // levels has not been provisioned + } while (KEY_CANCELED == status); return status; } @@ -716,13 +722,20 @@ CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id, status = usage_session_->GenerateReleaseRequest(&(*usage_info)[0], &server_url); - if (KEY_MESSAGE != status) { - LOGE("CdmEngine::GetUsageInfo: generate release request error: %d", - status); - usage_info->clear(); - return status; + switch (status) { + case KEY_MESSAGE: + break; + case KEY_CANCELED: // usage information not present in + usage_session_->DeleteLicense(); // OEMCrypto, delete and try again + usage_info->clear(); + break; + default: + LOGE("CdmEngine::GetUsageInfo: generate release request error: %d", + status); + usage_info->clear(); + break; } - return KEY_MESSAGE; + return status; } CdmResponseType CdmEngine::ReleaseAllUsageInfo(const std::string& app_id) { diff --git a/libwvdrmengine/cdm/core/src/cdm_session.cpp b/libwvdrmengine/cdm/core/src/cdm_session.cpp index 51006d21..35f692fa 100644 --- a/libwvdrmengine/cdm/core/src/cdm_session.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_session.cpp @@ -385,11 +385,11 @@ CdmResponseType CdmSession::Decrypt(const CdmDecryptionParameters& params) { // session keys. CdmResponseType CdmSession::GenerateRenewalRequest(CdmKeyMessage* key_request, std::string* server_url) { - if (!license_parser_->PrepareKeyUpdateRequest(true, key_request, - server_url)) { - LOGE("CdmSession::GenerateRenewalRequest: ERROR on prepare"); - return KEY_ERROR; - } + CdmResponseType status = + license_parser_->PrepareKeyUpdateRequest(true, key_request, server_url); + + if (KEY_MESSAGE != status) + return status; if (is_offline_) { offline_key_renewal_request_ = *key_request; @@ -413,8 +413,11 @@ CdmResponseType CdmSession::RenewKey(const CdmKeyResponse& key_response) { CdmResponseType CdmSession::GenerateReleaseRequest(CdmKeyMessage* key_request, std::string* server_url) { is_release_ = true; - if (!license_parser_->PrepareKeyUpdateRequest(false, key_request, server_url)) - return UNKNOWN_ERROR; + CdmResponseType status = + license_parser_->PrepareKeyUpdateRequest(false, key_request, server_url); + + if (KEY_MESSAGE != status) + return status; if (is_offline_) { // Mark license as being released if (!StoreLicense(DeviceFiles::kLicenseStateReleasing)) diff --git a/libwvdrmengine/cdm/core/src/crypto_session.cpp b/libwvdrmengine/cdm/core/src/crypto_session.cpp index 3514985b..4a82a557 100644 --- a/libwvdrmengine/cdm/core/src/crypto_session.cpp +++ b/libwvdrmengine/cdm/core/src/crypto_session.cpp @@ -716,13 +716,19 @@ CdmResponseType CryptoSession::DeactivateUsageInformation( OEMCryptoResult status = OEMCrypto_DeactivateUsageEntry(pst, provider_session_token.length()); - if (OEMCrypto_SUCCESS != status) { - LOGE("CryptoSession::DeactivateUsageInformation: Deactivate Usage Entry " - " error=%ld", + switch(status) { + case OEMCrypto_SUCCESS: + return NO_ERROR; + case OEMCrypto_ERROR_INVALID_CONTEXT: + LOGE("CryptoSession::DeactivateUsageInformation: Deactivate Usage Entry " + " invalid context error"); + return KEY_CANCELED; + default: + LOGE("CryptoSession::DeactivateUsageInformation: Deactivate Usage Entry " + " error=%ld", status); return UNKNOWN_ERROR; } - return NO_ERROR; } CdmResponseType CryptoSession::GenerateUsageReport( diff --git a/libwvdrmengine/cdm/core/src/license.cpp b/libwvdrmengine/cdm/core/src/license.cpp index baafccf5..8a6e9f5b 100644 --- a/libwvdrmengine/cdm/core/src/license.cpp +++ b/libwvdrmengine/cdm/core/src/license.cpp @@ -476,20 +476,21 @@ bool CdmLicense::PrepareKeyRequest(const InitializationData& init_data, return true; } -bool CdmLicense::PrepareKeyUpdateRequest(bool is_renewal, - CdmKeyMessage* signed_request, - std::string* server_url) { +CdmResponseType CdmLicense::PrepareKeyUpdateRequest( + bool is_renewal, + CdmKeyMessage* signed_request, + std::string* server_url) { if (!initialized_) { LOGE("CdmLicense::PrepareKeyUpdateRequest: not initialized"); - return false; + return UNKNOWN_ERROR; } if (!signed_request) { LOGE("CdmLicense::PrepareKeyUpdateRequest: No signed request provided"); - return false; + return UNKNOWN_ERROR; } if (!server_url) { LOGE("CdmLicense::PrepareKeyUpdateRequest: No server url provided"); - return false; + return UNKNOWN_ERROR; } LicenseRequest license_request; @@ -513,7 +514,7 @@ bool CdmLicense::PrepareKeyUpdateRequest(bool is_renewal, CdmResponseType status = session_->DeactivateUsageInformation(provider_session_token_); if (NO_ERROR != status) - return false; + return status; } std::string usage_report; @@ -526,7 +527,7 @@ bool CdmLicense::PrepareKeyUpdateRequest(bool is_renewal, if (NO_ERROR == status) current_license->set_session_usage_table_entry(usage_report); else - return false; + return KEY_ERROR; } } @@ -545,7 +546,7 @@ bool CdmLicense::PrepareKeyUpdateRequest(bool is_renewal, // of the license response. uint32_t nonce; if (!session_->GenerateNonce(&nonce)) { - return false; + return KEY_ERROR; } license_request.set_key_control_nonce(nonce); LOGD("PrepareKeyUpdateRequest: nonce=%u", nonce); @@ -559,13 +560,13 @@ bool CdmLicense::PrepareKeyUpdateRequest(bool is_renewal, std::string license_request_signature; if (!session_->PrepareRenewalRequest(serialized_license_req, &license_request_signature)) - return false; + return KEY_ERROR; if (license_request_signature.empty()) { LOGE( "CdmLicense::PrepareKeyUpdateRequest: empty license request" " signature"); - return false; + return KEY_ERROR; } // Put serialize license request and signature together @@ -576,7 +577,7 @@ bool CdmLicense::PrepareKeyUpdateRequest(bool is_renewal, signed_message.SerializeToString(signed_request); *server_url = server_url_; - return true; + return KEY_MESSAGE; } CdmResponseType CdmLicense::HandleKeyResponse( @@ -636,16 +637,16 @@ CdmResponseType CdmLicense::HandleKeyResponse( // Extract mac key std::string mac_key_iv; std::string mac_key; - if (license.policy().can_renew()) { - for (int i = 0; i < license.key_size(); ++i) { - if (license.key(i).type() == License_KeyContainer::SIGNING) { - mac_key_iv.assign(license.key(i).iv()); + for (int i = 0; i < license.key_size(); ++i) { + if (license.key(i).type() == License_KeyContainer::SIGNING) { + mac_key_iv.assign(license.key(i).iv()); - // Strip off PKCS#5 padding - mac_key.assign(license.key(i).key().data(), MAC_KEY_SIZE); - } + // Strip off PKCS#5 padding + mac_key.assign(license.key(i).key().data(), MAC_KEY_SIZE); } - + } + if (license.policy().can_renew() || + (mac_key_iv.size() != 0 || mac_key.size() != 0)) { if (mac_key_iv.size() != KEY_IV_SIZE || mac_key.size() != MAC_KEY_SIZE) { LOGE( "CdmLicense::HandleKeyResponse: mac key/iv size error" diff --git a/libwvdrmengine/cdm/test/cdm_extended_duration_test.cpp b/libwvdrmengine/cdm/test/cdm_extended_duration_test.cpp index 20fc230f..92ee187d 100644 --- a/libwvdrmengine/cdm/test/cdm_extended_duration_test.cpp +++ b/libwvdrmengine/cdm/test/cdm_extended_duration_test.cpp @@ -32,6 +32,8 @@ const uint32_t kMinute = 60; const uint32_t kClockTolerance = 5; const uint32_t kTwoMinutes = 120; +const uint32_t kMaxUsageTableSize = 50; + // Default license server, can be configured using --server command line option // Default key id (pssh), can be configured using --keyid command line option std::string g_client_auth; @@ -116,6 +118,29 @@ SubSampleInfo kEncryptedStreamingClip1SubSample = { "37326df26fa509343faa98dff667629f557873f1284903202e451227ef465a62"), wvcdm::a2b_hex("7362b5140c4ce0cd5f863858668d3f1a"), 0, 3}; +SubSampleInfo kEncryptedStreamingClip5SubSample = { + true, 1, true, true, false, + wvcdm::a2bs_hex("3AE243D83B93B3311A1D777FF5FBE01A"), + wvcdm::a2b_hex( + "934997779aa1aeb45d6ba8845f13786575d0adf85a5e93674d9597f8d4286ed7" + "dcce02f306e502bbd9f1cadf502f354038ca921276d158d911bdf3171d335b18" + "0ae0f9abece16ff31ee263228354f724da2f3723b19caa38ea02bd6563b01208" + "fb5bf57854ac0fe38d5883197ef90324b2721ff20fdcf9a53819515e6daa096e" + "70f6f5c1d29a4a13dafd127e2e1f761ea0e28fd451607552ecbaef5da3c780bc" + "aaf2667b4cc4f858f01d480cac9e32c3fbb5705e5d2adcceebefc2535c117208" + "e65f604799fc3d7223e16908550f287a4bea687008cb0064cf14d3aeedb8c705" + "09ebc5c2b8b5315f43c04d78d2f55f4b32c7d33e157114362106395cc0bb6d93"), + wvcdm::a2b_hex( + "2dd54eee1307753508e1f250d637044d6e8f5abf057dab73e9e95f83910e4efc" + "191c9bac63950f13fd51833dd94a4d03f2b64fb5c721970c418fe53fa6f74ad5" + "a6e16477a35c7aa6e28909b069cd25770ef80da20918fc30fe95fd5c87fd3522" + "1649de17ca2c7b3dc31f936f0cbdf97c7b1c15de3a86b279dc4b4de64943914a" + "99734556c4b7a1a0b022c1933cb0786068fc18d49fed2f2b49f3ac6d01c32d07" + "92175ce2844eaf9064e6a3fcffade038d690cbed81659351163a22432f0d0545" + "037e1c805d8e92a1272b4196ad0ce22f26bb80063137a8e454d3b97e2414283d" + "ed0716cd8bceb80cf59166a217006bd147c51b04dfb183088ce3f51e9b9f759e"), + wvcdm::a2b_hex("b358ab21ac90455bbf60490daad457e3"), 0, 3}; + SubSampleInfo kEncryptedOfflineClip2SubSample = { true, 1, true, true, false, wvcdm::a2bs_hex("3260F39E12CCF653529990168A3583FF"), @@ -150,6 +175,7 @@ std::string kOfflineClip2PstInitData = wvcdm::a2bs_hex( "EDEF8BA979D64ACEA3C827DCD51D21ED00000020" // Widevine system id "08011a0d7769646576696e655f74657374220d6f" // pssh data "66666c696e655f636c697032"); + } // namespace namespace wvcdm { @@ -167,6 +193,7 @@ class TestWvCdmClientPropertySet : public CdmClientPropertySet { session_sharing_id_(0) {} virtual ~TestWvCdmClientPropertySet() {} + virtual const std::string& app_id() const { return app_id_; } virtual const std::string& security_level() const { return security_level_; } virtual const std::string& service_certificate() const { return service_certificate_; @@ -177,6 +204,7 @@ class TestWvCdmClientPropertySet : public CdmClientPropertySet { } virtual uint32_t session_sharing_id() const { return session_sharing_id_; } + void set_app_id(const std::string& app_id) { app_id_ = app_id; } void set_security_level(const std::string& security_level) { if (!security_level.compare(QUERY_VALUE_SECURITY_LEVEL_L1) || !security_level.compare(QUERY_VALUE_SECURITY_LEVEL_L3)) { @@ -195,6 +223,7 @@ class TestWvCdmClientPropertySet : public CdmClientPropertySet { void set_session_sharing_id(uint32_t id) { session_sharing_id_ = id; } private: + std::string app_id_; std::string security_level_; std::string service_certificate_; bool use_privacy_mode_; @@ -794,6 +823,53 @@ TEST_F(WvCdmExtendedDurationTest, VerifyLicenseRenewalTest) { decryptor_.CloseSession(session_id_); } +TEST_F(WvCdmExtendedDurationTest, UsageOverflowTest) { + Provision(); + SubSampleInfo* data = &kEncryptedStreamingClip5SubSample; + TestWvCdmClientPropertySet client_property_set; + TestWvCdmClientPropertySet* property_set = NULL; + + CdmSecurityLevel security_level = GetDefaultSecurityLevel(); + DeviceFiles handle; + EXPECT_TRUE(handle.Init(security_level)); + File file; + handle.SetTestFile(&file); + EXPECT_TRUE(handle.DeleteAllUsageInfoForApp("")); + + for (size_t i = 0; i < kMaxUsageTableSize + 100; ++i) { + decryptor_.OpenSession(g_key_system, property_set, &session_id_); + std::string key_id = a2bs_hex( + "000000427073736800000000" // blob size and pssh + "EDEF8BA979D64ACEA3C827DCD51D21ED00000022" // Widevine system id + "08011a0d7769646576696e655f74657374220f73" // pssh data + "747265616d696e675f636c697035"); + + GenerateKeyRequest(key_id, kLicenseTypeStreaming); + VerifyKeyRequestResponse(g_license_server, g_client_auth, false); + decryptor_.CloseSession(session_id_); + } + + uint32_t num_usage_info = 0; + CdmUsageInfo usage_info; + CdmUsageInfoReleaseMessage release_msg; + CdmResponseType status = decryptor_.GetUsageInfo("", &usage_info); + EXPECT_EQ(usage_info.empty() ? NO_ERROR : KEY_MESSAGE, status); + while (usage_info.size() > 0) { + for (size_t i = 0; i < usage_info.size(); ++i) { + release_msg = + GetUsageInfoResponse(g_license_server, g_client_auth, usage_info[i]); + EXPECT_EQ(NO_ERROR, decryptor_.ReleaseUsageInfo(release_msg)); + } + status = decryptor_.GetUsageInfo("", &usage_info); + switch (status) { + case KEY_MESSAGE: EXPECT_FALSE(usage_info.empty()); break; + case NO_ERROR: EXPECT_TRUE(usage_info.empty()); break; + default: FAIL() << "GetUsageInfo failed with error " + << static_cast(status) ; break; + } + } +} + class WvCdmStreamingNoPstTest : public WvCdmExtendedDurationTest, public ::testing::WithParamInterface {}; diff --git a/libwvdrmengine/cdm/test/request_license_test.cpp b/libwvdrmengine/cdm/test/request_license_test.cpp index f931a1b9..0f8dc0e7 100644 --- a/libwvdrmengine/cdm/test/request_license_test.cpp +++ b/libwvdrmengine/cdm/test/request_license_test.cpp @@ -1200,6 +1200,68 @@ TEST_F(WvCdmRequestLicenseTest, RemoveKeys) { ASSERT_EQ(NO_ERROR, decryptor_.CloseSession(session_id_)); } +TEST_F(WvCdmRequestLicenseTest, UsageInfoRetryTest) { + Unprovision(); + Provision(kLevelDefault); + + CdmSecurityLevel security_level = GetDefaultSecurityLevel(); + std::string app_id = ""; + DeviceFiles handle; + EXPECT_TRUE(handle.Init(security_level)); + File file; + handle.SetTestFile(&file); + EXPECT_TRUE(handle.DeleteAllUsageInfoForApp(app_id)); + + SubSampleInfo* data = &usage_info_sub_samples_icp[0]; + decryptor_.OpenSession(g_key_system, NULL, &session_id_); + std::string key_id = a2bs_hex( + "000000427073736800000000" // blob size and pssh + "EDEF8BA979D64ACEA3C827DCD51D21ED00000022" // Widevine system id + "08011a0d7769646576696e655f74657374220f73" // pssh data + "747265616d696e675f636c697033"); + + GenerateKeyRequest(key_id, kLicenseTypeStreaming, NULL); + VerifyKeyRequestResponse(g_license_server, g_client_auth, false); + + 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; + 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_); + + uint32_t num_usage_info = 0; + CdmUsageInfo usage_info; + CdmUsageInfoReleaseMessage release_msg; + CdmResponseType status = decryptor_.GetUsageInfo(app_id, &usage_info); + EXPECT_EQ(usage_info.empty() ? NO_ERROR : KEY_MESSAGE, status); + + // Discard and retry to verify usage reports can be generated multiple times + // before release. + status = decryptor_.GetUsageInfo(app_id, &usage_info); + EXPECT_EQ(usage_info.empty() ? NO_ERROR : KEY_MESSAGE, status); + while (usage_info.size() > 0) { + for (size_t i = 0; i < usage_info.size(); ++i) { + release_msg = + GetUsageInfoResponse(g_license_server, g_client_auth, usage_info[i]); + EXPECT_EQ(NO_ERROR, decryptor_.ReleaseUsageInfo(release_msg)); + } + status = decryptor_.GetUsageInfo(app_id, &usage_info); + switch (status) { + case KEY_MESSAGE: EXPECT_FALSE(usage_info.empty()); break; + case NO_ERROR: EXPECT_TRUE(usage_info.empty()); break; + default: FAIL() << "GetUsageInfo failed with error " + << static_cast(status) ; break; + } + } +} + class WvCdmUsageInfoTest : public WvCdmRequestLicenseTest, public ::testing::WithParamInterface {};