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: