From 5194959c8cbcf248f51b0b47acf93b6289b24719 Mon Sep 17 00:00:00 2001 From: Alex Dale Date: Fri, 18 Oct 2024 15:43:56 -0700 Subject: [PATCH] Ignore certain errors on RemoveOfflineLicense(). [ Merge of http://go/wvgerrit/210652 ] The CDM API RemoveOfflineLicense() is used to remove an offline license by key set ID. From the app's perspective, removing the offline license should not depend on an app to be provisioned, or the license being loadable. However, internally, the CDM attempts to restore the license to lock out its usage entry. An issue arises when the license is not able to be restored, which will cause errors related to the restoration to be returned to the app. The license is still deleted in case of errors, but certain partners have experienced GTS failures when using the MediaDRM API removeOfflineLicense(). This change attempts to catch some of the common errors, but not all. If certain errors are encountered during the restoration process, the are not returned to the app. Additional error cases may be added later, depending on vendor feedback. Bug: 319055420 Bug: 357863269 Bug: 370195605 Bug: 288118860 Bug: 302049654 Bug: 346845333 Bug: 312595506 Bug: 345232142 Bug: 303261245 Bug: 287735498 Bug: 372105842 Test: WVTS on Oriole Change-Id: I020bbea30e5f6e0ae2777d8a1d4858c4f2af107b --- .../cdm/core/include/wv_cdm_types.h | 3 +- libwvdrmengine/cdm/core/src/cdm_engine.cpp | 48 +++- .../cdm/core/src/crypto_session.cpp | 2 +- libwvdrmengine/cdm/core/src/wv_cdm_types.cpp | 4 +- .../cdm/test/request_license_test.cpp | 232 +++++++++++++++--- libwvdrmengine/include/mapErrors-inl.h | 2 +- 6 files changed, 250 insertions(+), 41 deletions(-) diff --git a/libwvdrmengine/cdm/core/include/wv_cdm_types.h b/libwvdrmengine/cdm/core/include/wv_cdm_types.h index 5a5a12db..e4847271 100644 --- a/libwvdrmengine/cdm/core/include/wv_cdm_types.h +++ b/libwvdrmengine/cdm/core/include/wv_cdm_types.h @@ -381,7 +381,8 @@ enum CdmResponseEnum : int32_t { OUTPUT_TOO_LARGE_ERROR = 318, SESSION_LOST_STATE_ERROR = 319, GENERATE_DERIVED_KEYS_ERROR_2 = 320, - LOAD_DEVICE_RSA_KEY_ERROR = 321, + /* previously LOAD_DEVICE_RSA_KEY_ERROR = 321, */ + LOAD_DRM_PRIVATE_KEY_ERROR = 321, NONCE_GENERATION_ERROR = 322, GENERATE_SIGNATURE_ERROR = 323, UNKNOWN_CLIENT_TOKEN_TYPE = 324, diff --git a/libwvdrmengine/cdm/core/src/cdm_engine.cpp b/libwvdrmengine/cdm/core/src/cdm_engine.cpp index 424e5755..3d8ed126 100644 --- a/libwvdrmengine/cdm/core/src/cdm_engine.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_engine.cpp @@ -1504,20 +1504,38 @@ CdmResponseType CdmEngine::RemoveOfflineLicense( CdmResponseType sts = OpenKeySetSession(key_set_id, &property_set, nullptr /* event listener */); + if (sts == NEED_PROVISIONING) { + // It is possible that an app unprovisioned after requesting some + // licenses. RemoveOfflineLicense() should be allowed to work + // with or without provisioning. + LOGW("Not provisioned, deleting license: %s", IdToString(key_set_id)); + // TODO(b/372105842): Mark usage entry as unused. + handle.DeleteLicense(key_set_id); + return CdmResponseType(NO_ERROR); + } if (sts != NO_ERROR) { LOGE("OpenKeySetSession failed: status = %d", static_cast(sts)); handle.DeleteLicense(key_set_id); return sts; } + // Attempt to lock out the license's usage entry to prevent file + // restoration by generating a release request. + // This step should not directly effect the app, as the app is not + // requesting the release. See enumerated errors below. CdmSessionId session_id; CdmAppParameterMap dummy_app_params; const InitializationData dummy_init_data("", "", ""); - CdmKeyRequest key_request; - // Calling with no session_id is okay + CdmKeyRequest release_request_unused; + // Calling with no session_id is okay. + // License will be restored by GenerateKeyRequest(). sts = GenerateKeyRequest(session_id, key_set_id, dummy_init_data, - kLicenseTypeRelease, dummy_app_params, &key_request); + kLicenseTypeRelease, dummy_app_params, + &release_request_unused); + if (sts == KEY_MESSAGE) { + // Release was successfully generated, use CDM session + // to properly delete all license data. std::unique_lock lock(release_key_sets_lock_); CdmReleaseKeySetMap::iterator iter = release_key_sets_.find(key_set_id); if (iter == release_key_sets_.end()) { @@ -1535,10 +1553,32 @@ CdmResponseType CdmEngine::RemoveOfflineLicense( LOGW("License usage entry is missing, deleting license file"); handle.DeleteLicense(key_set_id); sts = CdmResponseType(NO_ERROR); + } else if (sts == LOAD_LICENSE_ERROR || sts == LOAD_DRM_PRIVATE_KEY_ERROR || + sts == RELEASE_LICENSE_ERROR_1 || sts == NEED_PROVISIONING) { + // It is possible that the DRM certificate associated with this + // license has been replaced/updated or that the root of trust has + // been updated (invalidating the DRM certificate). + // it will no longer be possible to load the license for release; + // and the file should simply be deleted. + LOGW("License could not be restored, deleting license file: status = %s", + sts.ToString().c_str()); + handle.DeleteLicense(key_set_id); + sts = CdmResponseType(NO_ERROR); + } else if (sts == GENERATE_SIGNATURE_ERROR) { + // It is possible that OEMCrypto does not have a key to generate + // a signature for release request. The app is trying to remove + // not release, so failure related to generating the release + // request should not cause this to fail. + LOGW("License could not be released, deleting license file: status = %s", + sts.ToString().c_str()); + handle.DeleteLicense(key_set_id); + sts = CdmResponseType(NO_ERROR); } if (sts != NO_ERROR) { - LOGE("GenerateKeyRequest failed: status = %d", static_cast(sts)); + // Errors not caught above should be treated as an error, + // and the license file should be deleted. + LOGE("GenerateKeyRequest failed: status = %s", sts.ToString().c_str()); handle.DeleteLicense(key_set_id); } CloseKeySetSession(key_set_id); diff --git a/libwvdrmengine/cdm/core/src/crypto_session.cpp b/libwvdrmengine/cdm/core/src/crypto_session.cpp index 939af6dd..ecb05e24 100644 --- a/libwvdrmengine/cdm/core/src/crypto_session.cpp +++ b/libwvdrmengine/cdm/core/src/crypto_session.cpp @@ -1391,7 +1391,7 @@ CdmResponseType CryptoSession::LoadCertificatePrivateKey( metrics_, oemcrypto_load_device_drm_key_, sts); }); - return MapOEMCryptoResult(sts, LOAD_DEVICE_RSA_KEY_ERROR, + return MapOEMCryptoResult(sts, LOAD_DRM_PRIVATE_KEY_ERROR, "LoadCertificatePrivateKey"); } diff --git a/libwvdrmengine/cdm/core/src/wv_cdm_types.cpp b/libwvdrmengine/cdm/core/src/wv_cdm_types.cpp index edc2edf2..28b3969d 100644 --- a/libwvdrmengine/cdm/core/src/wv_cdm_types.cpp +++ b/libwvdrmengine/cdm/core/src/wv_cdm_types.cpp @@ -719,8 +719,8 @@ const char* CdmResponseEnumToString(CdmResponseEnum cdm_response_enum) { return "SESSION_LOST_STATE_ERROR"; case GENERATE_DERIVED_KEYS_ERROR_2: return "GENERATE_DERIVED_KEYS_ERROR_2"; - case LOAD_DEVICE_RSA_KEY_ERROR: - return "LOAD_DEVICE_RSA_KEY_ERROR"; + case LOAD_DRM_PRIVATE_KEY_ERROR: + return "LOAD_DRM_PRIVATE_KEY_ERROR"; case NONCE_GENERATION_ERROR: return "NONCE_GENERATION_ERROR"; case GENERATE_SIGNATURE_ERROR: diff --git a/libwvdrmengine/cdm/test/request_license_test.cpp b/libwvdrmengine/cdm/test/request_license_test.cpp index b52bcb06..49d3a923 100644 --- a/libwvdrmengine/cdm/test/request_license_test.cpp +++ b/libwvdrmengine/cdm/test/request_license_test.cpp @@ -2129,11 +2129,13 @@ class WvCdmRequestLicenseTest : public WvCdmTestBase { return result; } - void Unprovision() { + void Unprovision() { Unprovision(kDefaultCdmIdentifier); } + + void Unprovision(const CdmIdentifier& identifier) { EXPECT_EQ(wvcdm::NO_ERROR, - decryptor_->Unprovision(kSecurityLevelL1, kDefaultCdmIdentifier)); + decryptor_->Unprovision(kSecurityLevelL1, identifier)); EXPECT_EQ(wvcdm::NO_ERROR, - decryptor_->Unprovision(kSecurityLevelL3, kDefaultCdmIdentifier)); + decryptor_->Unprovision(kSecurityLevelL3, identifier)); } bool IsProvisioned(const CdmIdentifier& identifier, @@ -2231,16 +2233,22 @@ class WvCdmRequestLicenseTest : public WvCdmTestBase { } CdmSecurityLevel GetDefaultSecurityLevel() { - std::string level = GetSecurityLevel(nullptr).c_str(); - CdmSecurityLevel security_level = kSecurityLevelUninitialized; - if (level.compare(wvcdm::QUERY_VALUE_SECURITY_LEVEL_L1) == 0) { - security_level = kSecurityLevelL1; - } else if (level.compare(wvcdm::QUERY_VALUE_SECURITY_LEVEL_L3) == 0) { - security_level = kSecurityLevelL3; - } else { - EXPECT_TRUE(false) << "Default Security level is undefined: " << level; + std::string security_level; + const CdmResponseType status = decryptor_->QueryStatus( + kLevelDefault, wvcdm::QUERY_KEY_SECURITY_LEVEL, &security_level); + if (status != NO_ERROR) { + ADD_FAILURE() << "Failed to obtain default security level: " + << status.ToString(); + return kSecurityLevelUninitialized; } - return security_level; + if (security_level == wvcdm::QUERY_VALUE_SECURITY_LEVEL_L1) { + return kSecurityLevelL1; + } + if (security_level == wvcdm::QUERY_VALUE_SECURITY_LEVEL_L3) { + return kSecurityLevelL3; + } + ADD_FAILURE() << "Undefined security level: " << security_level; + return kSecurityLevelUninitialized; } uint32_t QueryStatus(RequestedSecurityLevel security_level, @@ -6552,9 +6560,12 @@ TEST_F(WvCdmRequestLicenseTest, DISABLED_DecryptPathTest) { // not match the license's key set ID (possible for entry to have // been overwritten). TEST_F(WvCdmRequestLicenseTest, RemoveOfflineLicenseWithMissingUsageEntry) { - Unprovision(); - Provision(); const CdmSecurityLevel security_level = GetDefaultSecurityLevel(); + const RequestedSecurityLevel requested_security_level = + (security_level == kSecurityLevelL3) ? kLevel3 : kLevelDefault; + + Unprovision(kExampleIdentifier); + Provision(kExampleIdentifier, requested_security_level); FileSystem file_system; DeviceFiles handle(&file_system); @@ -6568,9 +6579,9 @@ TEST_F(WvCdmRequestLicenseTest, RemoveOfflineLicenseWithMissingUsageEntry) { // This license will be used to test how the CDM handles request to // remove it when its entry has been deleted. - decryptor_->OpenSession(config_.key_system(), nullptr, kDefaultCdmIdentifier, + decryptor_->OpenSession(config_.key_system(), nullptr, kExampleIdentifier, nullptr, &session_id_); - GenerateKeyRequest(key_id, kLicenseTypeOffline); + GenerateKeyRequest(key_id, kLicenseTypeOffline, kExampleIdentifier); VerifyKeyRequestResponse(config_.license_server(), client_auth); // Save the key set ID for check below. const std::string original_key_set_id(key_set_id_); @@ -6584,9 +6595,9 @@ TEST_F(WvCdmRequestLicenseTest, RemoveOfflineLicenseWithMissingUsageEntry) { EXPECT_EQ(DeviceFiles::kNoError, sub_result); // Re-provision. - Unprovision(); + Unprovision(kExampleIdentifier); handle.DeleteAllFiles(); - Provision(); + Provision(kExampleIdentifier, requested_security_level); // Part 1: Test when usage entry is out of range of the table. @@ -6597,26 +6608,26 @@ TEST_F(WvCdmRequestLicenseTest, RemoveOfflineLicenseWithMissingUsageEntry) { std::vector key_set_ids; EXPECT_EQ(wvcdm::NO_ERROR, - decryptor_->ListStoredLicenses( - security_level, kDefaultCdmIdentifier, &key_set_ids)); + decryptor_->ListStoredLicenses(security_level, kExampleIdentifier, + &key_set_ids)); // Note: It is possible that future changes to the CDM will cause this // check to fail (such by filtering results from ListStoreLicenses). EXPECT_THAT(key_set_ids, Contains(original_key_set_id)); EXPECT_EQ(wvcdm::NO_ERROR, decryptor_->RemoveOfflineLicense( - original_key_set_id, security_level, kDefaultCdmIdentifier)); + original_key_set_id, security_level, kExampleIdentifier)); // Verify license has been removed. EXPECT_EQ(wvcdm::NO_ERROR, - decryptor_->ListStoredLicenses( - security_level, kDefaultCdmIdentifier, &key_set_ids)); + decryptor_->ListStoredLicenses(security_level, kExampleIdentifier, + &key_set_ids)); EXPECT_THAT(key_set_ids, Not(Contains(original_key_set_id))); // Re-provision. - Unprovision(); + Unprovision(kExampleIdentifier); handle.DeleteAllFiles(); - Provision(); + Provision(kExampleIdentifier, requested_security_level); // Part 2: Test when the entry does not match the license's key set ID. @@ -6624,26 +6635,183 @@ TEST_F(WvCdmRequestLicenseTest, RemoveOfflineLicenseWithMissingUsageEntry) { EXPECT_EQ(DeviceFiles::kNoError, sub_result); // Request another license so that the usage table is not empty. - decryptor_->OpenSession(config_.key_system(), nullptr, kDefaultCdmIdentifier, + decryptor_->OpenSession(config_.key_system(), nullptr, kExampleIdentifier, nullptr, &session_id_); - GenerateKeyRequest(key_id, kLicenseTypeOffline); + GenerateKeyRequest(key_id, kLicenseTypeOffline, kExampleIdentifier); VerifyKeyRequestResponse(config_.license_server(), client_auth); decryptor_->CloseSession(session_id_); // Get list of existing offline licenses. EXPECT_EQ(wvcdm::NO_ERROR, - decryptor_->ListStoredLicenses( - security_level, kDefaultCdmIdentifier, &key_set_ids)); + decryptor_->ListStoredLicenses(security_level, kExampleIdentifier, + &key_set_ids)); EXPECT_THAT(key_set_ids, Contains(original_key_set_id)); EXPECT_EQ(wvcdm::NO_ERROR, decryptor_->RemoveOfflineLicense( - original_key_set_id, security_level, kDefaultCdmIdentifier)); + original_key_set_id, security_level, kExampleIdentifier)); // Verify license has been removed. EXPECT_EQ(wvcdm::NO_ERROR, - decryptor_->ListStoredLicenses( - security_level, kDefaultCdmIdentifier, &key_set_ids)); + decryptor_->ListStoredLicenses(security_level, kExampleIdentifier, + &key_set_ids)); + EXPECT_THAT(key_set_ids, Not(Contains(original_key_set_id))); +} + +TEST_F(WvCdmRequestLicenseTest, RemoveOfflineLicenseAfterUnprovisioning) { + const CdmSecurityLevel security_level = GetDefaultSecurityLevel(); + const RequestedSecurityLevel requested_security_level = + (security_level == kSecurityLevelL3) ? kLevel3 : kLevelDefault; + + Unprovision(kExampleIdentifier); + Provision(kExampleIdentifier, requested_security_level); + + std::string key_id; + std::string client_auth; + GetOfflineConfiguration(&key_id, &client_auth); + + // Setup: Request an offline license to create a valid license file. + // This license will be used to test how the CDM handles request to + // remove when the device has been unprovisioned. + + decryptor_->OpenSession(config_.key_system(), nullptr, kExampleIdentifier, + nullptr, &session_id_); + GenerateKeyRequest(key_id, kLicenseTypeOffline, kExampleIdentifier); + VerifyKeyRequestResponse(config_.license_server(), client_auth); + + // Save the key set ID for check below. + const std::string original_key_set_id(key_set_id_); + decryptor_->CloseSession(session_id_); + + // Unprovision, making the old DRM certificate unavailable. + Unprovision(kExampleIdentifier); + + std::vector key_set_ids; + EXPECT_EQ(wvcdm::NO_ERROR, + decryptor_->ListStoredLicenses(security_level, kExampleIdentifier, + &key_set_ids)); + // Note: It is possible that future changes to the CDM will cause this + // check to fail (such by filtering results from ListStoreLicenses). + EXPECT_THAT(key_set_ids, Contains(original_key_set_id)); + + // Remove offline license, without causing a provisioning error. + EXPECT_EQ(wvcdm::NO_ERROR, + decryptor_->RemoveOfflineLicense( + original_key_set_id, security_level, kExampleIdentifier)); + + // Verify license has been removed. + EXPECT_EQ(wvcdm::NO_ERROR, + decryptor_->ListStoredLicenses(security_level, kExampleIdentifier, + &key_set_ids)); + EXPECT_THAT(key_set_ids, Not(Contains(original_key_set_id))); +} + +TEST_F(WvCdmRequestLicenseTest, RemoveOfflineLicenseAfterReprovisioning) { + const CdmSecurityLevel security_level = GetDefaultSecurityLevel(); + const RequestedSecurityLevel requested_security_level = + (security_level == kSecurityLevelL3) ? kLevel3 : kLevelDefault; + + Unprovision(kExampleIdentifier); + Provision(kExampleIdentifier, requested_security_level); + + std::string key_id; + std::string client_auth; + GetOfflineConfiguration(&key_id, &client_auth); + + // Setup: Request an offline license to create a valid license file. + // This license will be used to test how the CDM handles request to + // remove when the device has been unprovisioned. + + decryptor_->OpenSession(config_.key_system(), nullptr, kExampleIdentifier, + nullptr, &session_id_); + GenerateKeyRequest(key_id, kLicenseTypeOffline, kExampleIdentifier); + VerifyKeyRequestResponse(config_.license_server(), client_auth); + + // Save the key set ID for check below. + const std::string original_key_set_id(key_set_id_); + decryptor_->CloseSession(session_id_); + + // Reprovision, making the old DRM certificate unavailable. + Unprovision(kExampleIdentifier); + Provision(kExampleIdentifier, requested_security_level); + + std::vector key_set_ids; + EXPECT_EQ(wvcdm::NO_ERROR, + decryptor_->ListStoredLicenses(security_level, kExampleIdentifier, + &key_set_ids)); + // Note: It is possible that future changes to the CDM will cause this + // check to fail (such by filtering results from ListStoreLicenses). + EXPECT_THAT(key_set_ids, Contains(original_key_set_id)); + + // Remove offline license, without causing a license load error. + EXPECT_EQ(wvcdm::NO_ERROR, + decryptor_->RemoveOfflineLicense( + original_key_set_id, security_level, kExampleIdentifier)); + + // Verify license has been removed. + EXPECT_EQ(wvcdm::NO_ERROR, + decryptor_->ListStoredLicenses(security_level, kExampleIdentifier, + &key_set_ids)); + EXPECT_THAT(key_set_ids, Not(Contains(original_key_set_id))); +} + +TEST_F(WvCdmRequestLicenseTest, RemoveUnlimitedOfflineLicense) { + const CdmSecurityLevel security_level = GetDefaultSecurityLevel(); + const RequestedSecurityLevel requested_security_level = + (security_level == kSecurityLevelL3) ? kLevel3 : kLevelDefault; + + Unprovision(kExampleIdentifier); + Provision(kExampleIdentifier, requested_security_level); + + std::string key_id; + std::string client_auth; + GetOfflineConfiguration(&key_id, &client_auth); + + // Override content ID. + key_id = wvutil::a2bs_hex( + "0000004d" // size = 77 + "70737368" // type = "pssh" + "00000000" // flags = None + "edef8ba979d64acea3c827dcd51d21ed" // system_id = Widevine + "0000002d" // pssh_data_size = 45 + // WidevinePsshData( + // algorithm = AESCTR, + // provider = "widevine_test" + // content_id = b"GTS_CAN_PERSIST_LICENSE_0S") + "08011a0d7769646576696e655f7465" + "7374221a4754535f43414e5f504552" + "534953545f4c4943454e53455f3053"); + + // Setup: Request an offline license to create a valid license file. + // This license will be used to test how the CDM handles request to + // remove when the device has been unprovisioned. + + decryptor_->OpenSession(config_.key_system(), nullptr, kExampleIdentifier, + nullptr, &session_id_); + GenerateKeyRequest(key_id, kLicenseTypeOffline, kExampleIdentifier); + VerifyKeyRequestResponse(config_.license_server(), client_auth); + + // Save the key set ID for check below. + const std::string original_key_set_id(key_set_id_); + decryptor_->CloseSession(session_id_); + + std::vector key_set_ids; + EXPECT_EQ(wvcdm::NO_ERROR, + decryptor_->ListStoredLicenses(security_level, kExampleIdentifier, + &key_set_ids)); + // Note: It is possible that future changes to the CDM will cause this + // check to fail (such by filtering results from ListStoreLicenses). + EXPECT_THAT(key_set_ids, Contains(original_key_set_id)); + + // Remove offline license, without causing a generate signature error. + EXPECT_EQ(wvcdm::NO_ERROR, + decryptor_->RemoveOfflineLicense( + original_key_set_id, security_level, kExampleIdentifier)); + + // Verify license has been removed. + EXPECT_EQ(wvcdm::NO_ERROR, + decryptor_->ListStoredLicenses(security_level, kExampleIdentifier, + &key_set_ids)); EXPECT_THAT(key_set_ids, Not(Contains(original_key_set_id))); } diff --git a/libwvdrmengine/include/mapErrors-inl.h b/libwvdrmengine/include/mapErrors-inl.h index c61d1e0b..05b9fbb8 100644 --- a/libwvdrmengine/include/mapErrors-inl.h +++ b/libwvdrmengine/include/mapErrors-inl.h @@ -386,7 +386,7 @@ static inline WvStatus mapCdmResponseType(wvcdm::CdmResponseType res) { case wvcdm::PRIVACY_MODE_ERROR_3: err = Status::MISSING_CERTIFICATE; break; - case wvcdm::LOAD_DEVICE_RSA_KEY_ERROR: + case wvcdm::LOAD_DRM_PRIVATE_KEY_ERROR: err = Status::PROVISIONING_CERTIFICATE_ERROR; break; case wvcdm::CERT_PROVISIONING_EMPTY_SERVICE_CERTIFICATE: