From d31a4dec5616fa5e1a109bc15578eb4cbf6c81e9 Mon Sep 17 00:00:00 2001 From: Rahul Frias Date: Thu, 30 Mar 2023 10:57:05 -0700 Subject: [PATCH] Force a reprovisioning on device renewal [ Merge of http://go/wvgerrit/169374 ] Device renewals used to require that OEMs remove provisioning certificates as part of the OTA update process. Instead, a change in system ID is relied upon to indicate a change in root of trust. If a change in System ID is detected, reprovisioning will be forced. This is not enabled for ATSC devices or L3 devices. For the latter a change in system ID may occurs without a change in RoT. Bug: 258361396 Test: GtsMediaTestCases Change-Id: I6e8b0b2149fc2ed5362a32bb6e869826f5fa8ef7 --- libwvdrmengine/cdm/core/include/cdm_session.h | 5 + libwvdrmengine/cdm/core/src/cdm_session.cpp | 51 ++++++ .../cdm/core/test/cdm_session_unittest.cpp | 148 +++++++++++++++++- 3 files changed, 196 insertions(+), 8 deletions(-) diff --git a/libwvdrmengine/cdm/core/include/cdm_session.h b/libwvdrmengine/cdm/core/include/cdm_session.h index b6e7152a..3e4e7083 100644 --- a/libwvdrmengine/cdm/core/include/cdm_session.h +++ b/libwvdrmengine/cdm/core/include/cdm_session.h @@ -28,6 +28,7 @@ class CdmClientPropertySet; class ServiceCertificate; class WvCdmEventListener; class CdmUsageTable; +class SystemIdExtractor; class CdmSession { public: @@ -255,11 +256,14 @@ class CdmSession { // true otherwise. bool VerifyOfflineUsageEntry(); + bool HasRootOfTrustBeenRenewed(); + // These setters are for testing only. Takes ownership of the pointers. void set_license_parser(CdmLicense* license_parser); void set_crypto_session(CryptoSession* crypto_session); void set_policy_engine(PolicyEngine* policy_engine); void set_file_handle(DeviceFiles* file_handle); + void set_system_id_extractor(SystemIdExtractor* extractor); // instance variables std::shared_ptr metrics_; @@ -276,6 +280,7 @@ class CdmSession { std::unique_ptr crypto_session_; std::unique_ptr policy_engine_; std::unique_ptr file_handle_; + std::unique_ptr mock_system_id_extractor_; bool license_received_; bool is_offline_; bool is_release_; diff --git a/libwvdrmengine/cdm/core/src/cdm_session.cpp b/libwvdrmengine/cdm/core/src/cdm_session.cpp index c0f20ded..81a8e1ed 100644 --- a/libwvdrmengine/cdm/core/src/cdm_session.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_session.cpp @@ -20,6 +20,7 @@ #include "log.h" #include "properties.h" #include "string_conversions.h" +#include "system_id_extractor.h" #include "wv_cdm_constants.h" #include "wv_cdm_event_listener.h" @@ -160,6 +161,9 @@ CdmResponseType CdmSession::Init(CdmClientPropertySet* cdm_client_property_set, if (!file_handle_->HasCertificate(atsc_mode_enabled_)) return CdmResponseType(NEED_PROVISIONING); + // Require reprovisioning if the root of trust has changed + if (HasRootOfTrustBeenRenewed()) return CdmResponseType(NEED_PROVISIONING); + if (forced_session_id) { key_set_id_ = *forced_session_id; } else { @@ -1243,6 +1247,49 @@ CdmResponseType CdmSession::LoadPrivateKey( } } +// Use a change in system ID as an indication that Root of Trust +// has been renewed. +bool CdmSession::HasRootOfTrustBeenRenewed() { + if (atsc_mode_enabled_) return false; + // Ignore System ID changes for L3 as the root of trust might not have + // changed even if the system ID has + if (crypto_session_->GetSecurityLevel() == kSecurityLevelL3) return false; + + std::string drm_certificate; + CryptoWrappedKey private_key; + uint32_t drm_cert_system_id; + if (file_handle_->RetrieveCertificate( + atsc_mode_enabled_, &drm_certificate, &private_key, nullptr, + &drm_cert_system_id) != DeviceFiles::kCertificateValid) { + LOGE("Failed to retrieve DRM certificate"); + return true; + } + + wvutil::FileSystem global_file_system; + SystemIdExtractor system_id_extractor(kLevelDefault, crypto_session_.get(), + &global_file_system); + + SystemIdExtractor* extractor = &system_id_extractor; + if (mock_system_id_extractor_) { + extractor = mock_system_id_extractor_.get(); + } + + uint32_t system_id; + if (!extractor->ExtractSystemId(&system_id)) { + LOGW("ExtractSystemId failed"); + return false; + } + + if (system_id == drm_cert_system_id) return false; + + LOGI( + "System Id changed from %d to %d. Removing certificates and " + "reprovisioning", + drm_cert_system_id, system_id); + file_handle_->RemoveCertificate(); + return true; +} + // For testing only - takes ownership of pointers void CdmSession::set_license_parser(CdmLicense* license_parser) { @@ -1262,4 +1309,8 @@ void CdmSession::set_policy_engine(PolicyEngine* policy_engine) { void CdmSession::set_file_handle(DeviceFiles* file_handle) { file_handle_.reset(file_handle); } + +void CdmSession::set_system_id_extractor(SystemIdExtractor* extractor) { + mock_system_id_extractor_.reset(extractor); +} } // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/test/cdm_session_unittest.cpp b/libwvdrmengine/cdm/core/test/cdm_session_unittest.cpp index 8a048870..40e84c62 100644 --- a/libwvdrmengine/cdm/core/test/cdm_session_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/cdm_session_unittest.cpp @@ -15,6 +15,7 @@ #include "properties.h" #include "service_certificate.h" #include "string_conversions.h" +#include "system_id_extractor.h" #include "test_base.h" #include "test_printers.h" #include "wv_cdm_constants.h" @@ -36,6 +37,8 @@ namespace wvcdm { namespace { const std::string kEmptyString; +const uint32_t kSystemId = 1234; +const uint32_t kUpdatedSystemId = 5678; const std::string kToken = wvutil::a2bs_hex( "0AAE02080212107E0A892DEEB021E7AF696B938BB1D5B1188B85AD9D05228E023082010A02" @@ -121,6 +124,7 @@ class MockDeviceFiles : public DeviceFiles { (bool, std::string*, CryptoWrappedKey*, std::string*, uint32_t*), (override)); MOCK_METHOD(bool, HasCertificate, (bool), (override)); + MOCK_METHOD(bool, RemoveCertificate, (), (override)); }; class MockCdmUsageTable : public CdmUsageTable { @@ -159,6 +163,27 @@ class MockCryptoSession : public TestCryptoSession { } }; +class TestCdmClientPropertySet : public CdmClientPropertySet { + public: + TestCdmClientPropertySet(bool atsc_mode) : atsc_mode_(atsc_mode) {} + ~TestCdmClientPropertySet() override {} + + const std::string& security_level() const override { return kEmptyString; } + bool use_privacy_mode() const override { return false; } + const std::string& service_certificate() const override { + return kEmptyString; + } + void set_service_certificate(const std::string& /* cert */) override {} + bool is_session_sharing_enabled() const override { return false; } + uint32_t session_sharing_id() const override { return 1; } + void set_session_sharing_id(uint32_t /* id */) override {} + const std::string& app_id() const override { return kEmptyString; } + bool use_atsc_mode() const override { return atsc_mode_; } + + private: + bool atsc_mode_; +}; + class MockPolicyEngine : public PolicyEngine { public: MockPolicyEngine(CryptoSession* crypto_session) @@ -177,6 +202,14 @@ class MockCdmLicense : public CdmLicense { MOCK_METHOD(std::string, provider_session_token, (), (override)); }; +class MockSystemIdExtractor : public SystemIdExtractor { + public: + MockSystemIdExtractor(CryptoSession* crypto_session, wvutil::FileSystem* fs) + : SystemIdExtractor(kLevelDefault, crypto_session, fs) {} + + MOCK_METHOD(bool, ExtractSystemId, (uint32_t*), (override)); +}; + } // namespace class CdmSessionTest : public WvCdmTestBase { @@ -194,6 +227,9 @@ class CdmSessionTest : public WvCdmTestBase { cdm_session_->set_policy_engine(policy_engine_); file_handle_ = new MockDeviceFiles(); cdm_session_->set_file_handle(file_handle_); + system_id_extractor_ = + new MockSystemIdExtractor(crypto_session_, &global_file_system_); + cdm_session_->set_system_id_extractor(system_id_extractor_); } void TearDown() override { @@ -210,24 +246,32 @@ class CdmSessionTest : public WvCdmTestBase { MockPolicyEngine* policy_engine_; MockDeviceFiles* file_handle_; MockCdmUsageTable usage_table_; + MockSystemIdExtractor* system_id_extractor_; + wvutil::FileSystem global_file_system_; }; TEST_F(CdmSessionTest, InitWithBuiltInCertificate) { Sequence crypto_session_seq; CdmSecurityLevel level = kSecurityLevelL1; EXPECT_CALL(*crypto_session_, Open(Eq(kLevelDefault))) - .InSequence(crypto_session_seq) .WillOnce(Return(CdmResponseType(NO_ERROR))); EXPECT_CALL(*crypto_session_, GetSecurityLevel()) - .InSequence(crypto_session_seq) - .WillOnce(Return(level)); + .WillRepeatedly(Return(level)); + EXPECT_CALL(*crypto_session_, HasUsageTableSupport(NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(true), Return(true))); EXPECT_CALL(*file_handle_, Init(Eq(level))).WillOnce(Return(true)); EXPECT_CALL(*file_handle_, HasCertificate(false)).WillOnce(Return(true)); + EXPECT_CALL(*file_handle_, RetrieveCertificate(false, NotNull(), _, _, _)) + .WillOnce(DoAll(SetArgPointee<4>(kSystemId), + Return(DeviceFiles::kCertificateValid))); + EXPECT_CALL(*file_handle_, RemoveCertificate()).Times(0); EXPECT_CALL(*license_parser_, Init(false, Eq(kEmptyString), Eq(crypto_session_), Eq(policy_engine_))) .WillOnce(Return(true)); EXPECT_CALL(*license_parser_, provider_session_token()) .WillRepeatedly(Return("Mock provider session token")); + EXPECT_CALL(*system_id_extractor_, ExtractSystemId(NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kSystemId), Return(true))); ASSERT_EQ(NO_ERROR, cdm_session_->Init(nullptr)); } @@ -236,13 +280,20 @@ TEST_F(CdmSessionTest, InitWithCertificate) { Sequence crypto_session_seq; CdmSecurityLevel level = kSecurityLevelL1; EXPECT_CALL(*crypto_session_, Open(Eq(kLevelDefault))) - .InSequence(crypto_session_seq) .WillOnce(Return(CdmResponseType(NO_ERROR))); EXPECT_CALL(*crypto_session_, GetSecurityLevel()) - .InSequence(crypto_session_seq) - .WillOnce(Return(level)); + .WillRepeatedly(Return(level)); + EXPECT_CALL(*crypto_session_, HasUsageTableSupport(NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(true), Return(true))); EXPECT_CALL(*file_handle_, Init(Eq(level))).WillOnce(Return(true)); EXPECT_CALL(*file_handle_, HasCertificate(false)).WillOnce(Return(true)); + EXPECT_CALL(*file_handle_, RetrieveCertificate(false, NotNull(), _, _, _)) + .WillOnce(DoAll(SetArgPointee<4>(kSystemId), + Return(DeviceFiles::kCertificateValid))); + EXPECT_CALL(*file_handle_, RemoveCertificate()).Times(0); + EXPECT_CALL(*system_id_extractor_, ExtractSystemId(NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kSystemId), Return(true))); + EXPECT_CALL(*license_parser_, Init(false, Eq(kEmptyString), Eq(crypto_session_), Eq(policy_engine_))) .WillOnce(Return(true)); @@ -260,14 +311,19 @@ TEST_F(CdmSessionTest, ReInitFail) { .WillOnce(Return(CdmResponseType(NO_ERROR))); EXPECT_CALL(*crypto_session_, GetSecurityLevel()) .InSequence(crypto_session_seq) - .WillOnce(Return(level)); + .WillRepeatedly(Return(level)); EXPECT_CALL(*file_handle_, Init(Eq(level))).WillOnce(Return(true)); EXPECT_CALL(*file_handle_, HasCertificate(false)).WillOnce(Return(true)); + EXPECT_CALL(*file_handle_, RetrieveCertificate(false, NotNull(), _, _, _)) + .WillOnce(DoAll(SetArgPointee<4>(kSystemId), + Return(DeviceFiles::kCertificateValid))); EXPECT_CALL(*license_parser_, Init(false, Eq(kEmptyString), Eq(crypto_session_), Eq(policy_engine_))) .WillOnce(Return(true)); EXPECT_CALL(*license_parser_, provider_session_token()) .WillRepeatedly(Return("Mock provider session token")); + EXPECT_CALL(*system_id_extractor_, ExtractSystemId(NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kSystemId), Return(true))); ASSERT_EQ(NO_ERROR, cdm_session_->Init(nullptr)); ASSERT_NE(NO_ERROR, cdm_session_->Init(nullptr)); @@ -282,6 +338,77 @@ TEST_F(CdmSessionTest, InitFailCryptoError) { ASSERT_EQ(UNKNOWN_ERROR, cdm_session_->Init(nullptr)); } +TEST_F(CdmSessionTest, Init_SystemIdChanged_NeedsProvisioning) { + Sequence crypto_session_seq; + CdmSecurityLevel level = kSecurityLevelL1; + EXPECT_CALL(*crypto_session_, Open(Eq(kLevelDefault))) + .WillOnce(Return(CdmResponseType(NO_ERROR))); + EXPECT_CALL(*crypto_session_, GetSecurityLevel()) + .WillRepeatedly(Return(level)); + EXPECT_CALL(*crypto_session_, HasUsageTableSupport(NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(true), Return(true))); + EXPECT_CALL(*file_handle_, Init(Eq(level))).WillOnce(Return(true)); + EXPECT_CALL(*file_handle_, HasCertificate(false)).WillOnce(Return(true)); + EXPECT_CALL(*file_handle_, RetrieveCertificate(false, NotNull(), _, _, _)) + .WillOnce(DoAll(SetArgPointee<4>(kSystemId), + Return(DeviceFiles::kCertificateValid))); + EXPECT_CALL(*file_handle_, RemoveCertificate()).WillOnce(Return(true)); + EXPECT_CALL(*system_id_extractor_, ExtractSystemId(NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kUpdatedSystemId), Return(true))); + + EXPECT_CALL(*license_parser_, provider_session_token()) + .WillRepeatedly(Return("Mock provider session token")); + + ASSERT_EQ(NEED_PROVISIONING, cdm_session_->Init(nullptr)); +} + +TEST_F(CdmSessionTest, Init_AtscSystemIdChanged_NoReProvisionNeeded) { + Sequence crypto_session_seq; + CdmSecurityLevel level = kSecurityLevelL3; + EXPECT_CALL(*crypto_session_, Open(Eq(kLevelDefault))) + .WillOnce(Return(CdmResponseType(NO_ERROR))); + EXPECT_CALL(*crypto_session_, GetSecurityLevel()) + .WillRepeatedly(Return(level)); + EXPECT_CALL(*crypto_session_, HasUsageTableSupport(NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(true), Return(true))); + EXPECT_CALL(*file_handle_, Init(Eq(level))).WillOnce(Return(true)); + EXPECT_CALL(*file_handle_, HasCertificate(true)).WillOnce(Return(true)); + EXPECT_CALL(*file_handle_, RemoveCertificate()).Times(0); + EXPECT_CALL(*system_id_extractor_, ExtractSystemId(NotNull())).Times(0); + + EXPECT_CALL(*license_parser_, Init(false, Eq(kEmptyString), + Eq(crypto_session_), Eq(policy_engine_))) + .WillOnce(Return(true)); + EXPECT_CALL(*license_parser_, provider_session_token()) + .WillRepeatedly(Return("Mock provider session token")); + + TestCdmClientPropertySet atsc_property_set(true); + ASSERT_EQ(NO_ERROR, cdm_session_->Init(&atsc_property_set)); +} + +TEST_F(CdmSessionTest, Init_L3SystemIdChanged_NoReProvisionNeeded) { + Sequence crypto_session_seq; + CdmSecurityLevel level = kSecurityLevelL3; + EXPECT_CALL(*crypto_session_, Open(Eq(kLevelDefault))) + .WillOnce(Return(CdmResponseType(NO_ERROR))); + EXPECT_CALL(*crypto_session_, GetSecurityLevel()) + .WillRepeatedly(Return(level)); + EXPECT_CALL(*crypto_session_, HasUsageTableSupport(NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(true), Return(true))); + EXPECT_CALL(*file_handle_, Init(Eq(level))).WillOnce(Return(true)); + EXPECT_CALL(*file_handle_, HasCertificate(false)).WillOnce(Return(true)); + EXPECT_CALL(*file_handle_, RemoveCertificate()).Times(0); + EXPECT_CALL(*system_id_extractor_, ExtractSystemId(NotNull())).Times(0); + + EXPECT_CALL(*license_parser_, Init(false, Eq(kEmptyString), + Eq(crypto_session_), Eq(policy_engine_))) + .WillOnce(Return(true)); + EXPECT_CALL(*license_parser_, provider_session_token()) + .WillRepeatedly(Return("Mock provider session token")); + + ASSERT_EQ(NO_ERROR, cdm_session_->Init(nullptr)); +} + TEST_F(CdmSessionTest, UpdateUsageEntry) { // Setup common expectations for initializing the CdmSession object. Sequence crypto_session_seq; @@ -291,14 +418,19 @@ TEST_F(CdmSessionTest, UpdateUsageEntry) { .WillOnce(Return(CdmResponseType(NO_ERROR))); EXPECT_CALL(*crypto_session_, GetSecurityLevel()) .InSequence(crypto_session_seq) - .WillOnce(Return(level)); + .WillRepeatedly(Return(level)); EXPECT_CALL(*file_handle_, Init(Eq(level))).WillOnce(Return(true)); EXPECT_CALL(*file_handle_, HasCertificate(false)).WillOnce(Return(true)); + EXPECT_CALL(*file_handle_, RetrieveCertificate(false, NotNull(), _, _, _)) + .WillOnce(DoAll(SetArgPointee<4>(kSystemId), + Return(DeviceFiles::kCertificateValid))); EXPECT_CALL(*crypto_session_, GetUsageTable()) .WillOnce(Return(&usage_table_)); EXPECT_CALL(*license_parser_, Init(false, Eq(kEmptyString), Eq(crypto_session_), Eq(policy_engine_))) .WillOnce(Return(true)); + EXPECT_CALL(*system_id_extractor_, ExtractSystemId(NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kSystemId), Return(false))); // Set up mocks and expectations for the UpdateUsageEntryInformation call. EXPECT_CALL(*crypto_session_, HasUsageTableSupport(_))