Wire NetworkIsolationKey up to TransportSecurityPersister.

Expect-CT header information will, when the SSL cache is split by NIK,
be scoped to a particular NetworkIsolationKey. Uploaded reports will
also use the corresponding NetworkIsolationKey.

Bug: 969893, 1082280
Change-Id: I4c1af5653872778f4bea19cfd51756e2f840d712
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2220908
Commit-Queue: Matt Menke <[email protected]>
Reviewed-by: Emily Stark <[email protected]>
Cr-Commit-Position: refs/heads/master@{#775878}
diff --git a/net/http/transport_security_persister_unittest.cc b/net/http/transport_security_persister_unittest.cc
index cf1e286a..c9a893b 100644
--- a/net/http/transport_security_persister_unittest.cc
+++ b/net/http/transport_security_persister_unittest.cc
@@ -16,10 +16,13 @@
 #include "base/run_loop.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "net/base/features.h"
+#include "net/base/network_isolation_key.h"
 #include "net/http/transport_security_state.h"
 #include "net/test/test_with_task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
+#include "url/origin.h"
 
 namespace net {
 
@@ -27,7 +30,9 @@
 
 const char kReportUri[] = "http://www.example.test/report";
 
-class TransportSecurityPersisterTest : public testing::Test,
+// The bool indicates whether kPartitionExpectCTStateByNetworkIsolationKey
+// should be enabled.
+class TransportSecurityPersisterTest : public ::testing::TestWithParam<bool>,
                                        public WithTaskEnvironment {
  public:
   TransportSecurityPersisterTest()
@@ -51,18 +56,32 @@
         base::ThreadPool::CreateSequencedTaskRunner(
             {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
              base::TaskShutdownBehavior::BLOCK_SHUTDOWN}));
+    // This feature is used in initializing |state_|.
+    if (partition_expect_ct_state()) {
+      feature_list_.InitAndEnableFeature(
+          features::kPartitionExpectCTStateByNetworkIsolationKey);
+    } else {
+      feature_list_.InitAndDisableFeature(
+          features::kPartitionExpectCTStateByNetworkIsolationKey);
+    }
+    state_ = std::make_unique<TransportSecurityState>();
     persister_ = std::make_unique<TransportSecurityPersister>(
-        &state_, temp_dir_.GetPath(), std::move(background_runner));
+        state_.get(), temp_dir_.GetPath(), std::move(background_runner));
   }
 
+  bool partition_expect_ct_state() const { return GetParam(); }
+
  protected:
   base::ScopedTempDir temp_dir_;
-  TransportSecurityState state_;
+  base::test::ScopedFeatureList feature_list_;
+  std::unique_ptr<TransportSecurityState> state_;
   std::unique_ptr<TransportSecurityPersister> persister_;
 };
 
+INSTANTIATE_TEST_SUITE_P(All, TransportSecurityPersisterTest, testing::Bool());
+
 // Tests that LoadEntries() clears existing non-static entries.
-TEST_F(TransportSecurityPersisterTest, LoadEntriesClearsExistingState) {
+TEST_P(TransportSecurityPersisterTest, LoadEntriesClearsExistingState) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitAndEnableFeature(
       TransportSecurityState::kDynamicExpectCTFeature);
@@ -74,22 +93,25 @@
   const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000);
   static const char kYahooDomain[] = "yahoo.com";
 
-  EXPECT_FALSE(state_.GetDynamicSTSState(kYahooDomain, &sts_state));
+  EXPECT_FALSE(state_->GetDynamicSTSState(kYahooDomain, &sts_state));
 
-  state_.AddHSTS(kYahooDomain, expiry, false /* include subdomains */);
-  state_.AddExpectCT(kYahooDomain, expiry, true /* enforce */, GURL());
+  state_->AddHSTS(kYahooDomain, expiry, false /* include subdomains */);
+  state_->AddExpectCT(kYahooDomain, expiry, true /* enforce */, GURL(),
+                      NetworkIsolationKey());
 
-  EXPECT_TRUE(state_.GetDynamicSTSState(kYahooDomain, &sts_state));
-  EXPECT_TRUE(state_.GetDynamicExpectCTState(kYahooDomain, &expect_ct_state));
+  EXPECT_TRUE(state_->GetDynamicSTSState(kYahooDomain, &sts_state));
+  EXPECT_TRUE(state_->GetDynamicExpectCTState(
+      kYahooDomain, NetworkIsolationKey(), &expect_ct_state));
 
   EXPECT_TRUE(persister_->LoadEntries("{\"version\":2}", &data_in_old_format));
   EXPECT_FALSE(data_in_old_format);
 
-  EXPECT_FALSE(state_.GetDynamicSTSState(kYahooDomain, &sts_state));
-  EXPECT_FALSE(state_.GetDynamicExpectCTState(kYahooDomain, &expect_ct_state));
+  EXPECT_FALSE(state_->GetDynamicSTSState(kYahooDomain, &sts_state));
+  EXPECT_FALSE(state_->GetDynamicExpectCTState(
+      kYahooDomain, NetworkIsolationKey(), &expect_ct_state));
 }
 
-TEST_F(TransportSecurityPersisterTest, SerializeData1) {
+TEST_P(TransportSecurityPersisterTest, SerializeData1) {
   std::string output;
   bool data_in_old_format;
 
@@ -98,16 +120,16 @@
   EXPECT_FALSE(data_in_old_format);
 }
 
-TEST_F(TransportSecurityPersisterTest, SerializeData2) {
+TEST_P(TransportSecurityPersisterTest, SerializeData2) {
   TransportSecurityState::STSState sts_state;
   const base::Time current_time(base::Time::Now());
   const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000);
   static const char kYahooDomain[] = "yahoo.com";
 
-  EXPECT_FALSE(state_.GetDynamicSTSState(kYahooDomain, &sts_state));
+  EXPECT_FALSE(state_->GetDynamicSTSState(kYahooDomain, &sts_state));
 
   bool include_subdomains = true;
-  state_.AddHSTS(kYahooDomain, expiry, include_subdomains);
+  state_->AddHSTS(kYahooDomain, expiry, include_subdomains);
 
   std::string output;
   bool data_in_old_format;
@@ -115,21 +137,21 @@
   EXPECT_TRUE(persister_->LoadEntries(output, &data_in_old_format));
   EXPECT_FALSE(data_in_old_format);
 
-  EXPECT_TRUE(state_.GetDynamicSTSState(kYahooDomain, &sts_state));
+  EXPECT_TRUE(state_->GetDynamicSTSState(kYahooDomain, &sts_state));
   EXPECT_EQ(sts_state.upgrade_mode,
             TransportSecurityState::STSState::MODE_FORCE_HTTPS);
-  EXPECT_TRUE(state_.GetDynamicSTSState("foo.yahoo.com", &sts_state));
+  EXPECT_TRUE(state_->GetDynamicSTSState("foo.yahoo.com", &sts_state));
   EXPECT_EQ(sts_state.upgrade_mode,
             TransportSecurityState::STSState::MODE_FORCE_HTTPS);
-  EXPECT_TRUE(state_.GetDynamicSTSState("foo.bar.yahoo.com", &sts_state));
+  EXPECT_TRUE(state_->GetDynamicSTSState("foo.bar.yahoo.com", &sts_state));
   EXPECT_EQ(sts_state.upgrade_mode,
             TransportSecurityState::STSState::MODE_FORCE_HTTPS);
-  EXPECT_TRUE(state_.GetDynamicSTSState("foo.bar.baz.yahoo.com", &sts_state));
+  EXPECT_TRUE(state_->GetDynamicSTSState("foo.bar.baz.yahoo.com", &sts_state));
   EXPECT_EQ(sts_state.upgrade_mode,
             TransportSecurityState::STSState::MODE_FORCE_HTTPS);
 }
 
-TEST_F(TransportSecurityPersisterTest, SerializeData3) {
+TEST_P(TransportSecurityPersisterTest, SerializeData3) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitAndEnableFeature(
       TransportSecurityState::kDynamicExpectCTFeature);
@@ -138,26 +160,27 @@
   base::Time expiry =
       base::Time::Now() + base::TimeDelta::FromSeconds(1000);
   bool include_subdomains = false;
-  state_.AddHSTS("www.example.com", expiry, include_subdomains);
-  state_.AddExpectCT("www.example.com", expiry, true /* enforce */, GURL());
+  state_->AddHSTS("www.example.com", expiry, include_subdomains);
+  state_->AddExpectCT("www.example.com", expiry, true /* enforce */, GURL(),
+                      NetworkIsolationKey());
 
   // Add another entry.
   expiry =
       base::Time::Now() + base::TimeDelta::FromSeconds(3000);
-  state_.AddHSTS("www.example.net", expiry, include_subdomains);
-  state_.AddExpectCT("www.example.net", expiry, false /* enforce */,
-                     report_uri);
+  state_->AddHSTS("www.example.net", expiry, include_subdomains);
+  state_->AddExpectCT("www.example.net", expiry, false /* enforce */,
+                      report_uri, NetworkIsolationKey());
 
   // Save a copy of everything.
   std::set<std::string> sts_saved;
-  TransportSecurityState::STSStateIterator sts_iter(state_);
+  TransportSecurityState::STSStateIterator sts_iter(*state_);
   while (sts_iter.HasNext()) {
     sts_saved.insert(sts_iter.hostname());
     sts_iter.Advance();
   }
 
   std::set<std::string> expect_ct_saved;
-  TransportSecurityState::ExpectCTStateIterator expect_ct_iter(state_);
+  TransportSecurityState::ExpectCTStateIterator expect_ct_iter(*state_);
   while (expect_ct_iter.HasNext()) {
     expect_ct_saved.insert(expect_ct_iter.hostname());
     expect_ct_iter.Advance();
@@ -168,7 +191,7 @@
 
   // Persist the data to the file.
   base::RunLoop run_loop;
-  persister_->WriteNow(&state_, run_loop.QuitClosure());
+  persister_->WriteNow(state_.get(), run_loop.QuitClosure());
   run_loop.Run();
 
   // Read the data back.
@@ -182,7 +205,7 @@
 
   // Check that states are the same as saved.
   size_t count = 0;
-  TransportSecurityState::STSStateIterator sts_iter2(state_);
+  TransportSecurityState::STSStateIterator sts_iter2(*state_);
   while (sts_iter2.HasNext()) {
     count++;
     sts_iter2.Advance();
@@ -190,7 +213,7 @@
   EXPECT_EQ(count, sts_saved.size());
 
   count = 0;
-  TransportSecurityState::ExpectCTStateIterator expect_ct_iter2(state_);
+  TransportSecurityState::ExpectCTStateIterator expect_ct_iter2(*state_);
   while (expect_ct_iter2.HasNext()) {
     count++;
     expect_ct_iter2.Advance();
@@ -198,7 +221,7 @@
   EXPECT_EQ(count, expect_ct_saved.size());
 }
 
-TEST_F(TransportSecurityPersisterTest, DeserializeBadData) {
+TEST_P(TransportSecurityPersisterTest, DeserializeBadData) {
   bool data_in_old_format;
   EXPECT_FALSE(persister_->LoadEntries("", &data_in_old_format));
   EXPECT_FALSE(persister_->LoadEntries("Foopy", &data_in_old_format));
@@ -207,7 +230,7 @@
   EXPECT_FALSE(persister_->LoadEntries("{\"version\":1}", &data_in_old_format));
 }
 
-TEST_F(TransportSecurityPersisterTest, DeserializeDataOldWithoutCreationDate) {
+TEST_P(TransportSecurityPersisterTest, DeserializeDataOldWithoutCreationDate) {
   const char kDomain[] = "example.test";
 
   // This is an old-style piece of transport state JSON, which has no creation
@@ -225,14 +248,14 @@
   EXPECT_TRUE(data_in_old_format);
 
   TransportSecurityState::STSState sts_state;
-  EXPECT_TRUE(state_.GetDynamicSTSState(kDomain, &sts_state));
+  EXPECT_TRUE(state_->GetDynamicSTSState(kDomain, &sts_state));
   EXPECT_EQ(kDomain, sts_state.domain);
   EXPECT_FALSE(sts_state.include_subdomains);
   EXPECT_EQ(TransportSecurityState::STSState::MODE_FORCE_HTTPS,
             sts_state.upgrade_mode);
 }
 
-TEST_F(TransportSecurityPersisterTest, DeserializeDataOldMergedDictionary) {
+TEST_P(TransportSecurityPersisterTest, DeserializeDataOldMergedDictionary) {
   const char kStsDomain[] = "sts.test";
   const char kExpectCTDomain[] = "expect_ct.test";
   const GURL kExpectCTReportUri = GURL("https://expect_ct.test/report_uri");
@@ -280,7 +303,7 @@
 
   // kStsDomain should only have HSTS information.
   TransportSecurityState::STSState sts_state;
-  EXPECT_TRUE(state_.GetDynamicSTSState(kStsDomain, &sts_state));
+  EXPECT_TRUE(state_->GetDynamicSTSState(kStsDomain, &sts_state));
   EXPECT_EQ(kStsDomain, sts_state.domain);
   EXPECT_FALSE(sts_state.include_subdomains);
   EXPECT_EQ(TransportSecurityState::STSState::MODE_FORCE_HTTPS,
@@ -288,14 +311,15 @@
   EXPECT_LT(base::Time::Now(), sts_state.last_observed);
   EXPECT_LT(sts_state.last_observed, sts_state.expiry);
   TransportSecurityState::ExpectCTState expect_ct_state;
-  EXPECT_FALSE(state_.GetDynamicExpectCTState(kStsDomain, &expect_ct_state));
+  EXPECT_FALSE(state_->GetDynamicExpectCTState(
+      kStsDomain, NetworkIsolationKey(), &expect_ct_state));
 
   // kExpectCTDomain should only have HSTS information.
   sts_state = TransportSecurityState::STSState();
-  EXPECT_FALSE(state_.GetDynamicSTSState(kExpectCTDomain, &sts_state));
+  EXPECT_FALSE(state_->GetDynamicSTSState(kExpectCTDomain, &sts_state));
   expect_ct_state = TransportSecurityState::ExpectCTState();
-  EXPECT_TRUE(
-      state_.GetDynamicExpectCTState(kExpectCTDomain, &expect_ct_state));
+  EXPECT_TRUE(state_->GetDynamicExpectCTState(
+      kExpectCTDomain, NetworkIsolationKey(), &expect_ct_state));
   EXPECT_EQ(kExpectCTReportUri, expect_ct_state.report_uri);
   EXPECT_TRUE(expect_ct_state.enforce);
   EXPECT_LT(base::Time::Now(), expect_ct_state.last_observed);
@@ -303,7 +327,7 @@
 
   // kBothDomain should have HSTS and ExpectCT information.
   sts_state = TransportSecurityState::STSState();
-  EXPECT_TRUE(state_.GetDynamicSTSState(kBothDomain, &sts_state));
+  EXPECT_TRUE(state_->GetDynamicSTSState(kBothDomain, &sts_state));
   EXPECT_EQ(kBothDomain, sts_state.domain);
   EXPECT_TRUE(sts_state.include_subdomains);
   EXPECT_EQ(TransportSecurityState::STSState::MODE_FORCE_HTTPS,
@@ -311,7 +335,8 @@
   EXPECT_LT(base::Time::Now(), sts_state.last_observed);
   EXPECT_LT(sts_state.last_observed, sts_state.expiry);
   expect_ct_state = TransportSecurityState::ExpectCTState();
-  EXPECT_TRUE(state_.GetDynamicExpectCTState(kBothDomain, &expect_ct_state));
+  EXPECT_TRUE(state_->GetDynamicExpectCTState(
+      kBothDomain, NetworkIsolationKey(), &expect_ct_state));
   EXPECT_TRUE(expect_ct_state.report_uri.is_empty());
   EXPECT_TRUE(expect_ct_state.enforce);
   EXPECT_LT(base::Time::Now(), expect_ct_state.last_observed);
@@ -319,7 +344,7 @@
 }
 
 // Tests that dynamic Expect-CT state is serialized and deserialized correctly.
-TEST_F(TransportSecurityPersisterTest, ExpectCT) {
+TEST_P(TransportSecurityPersisterTest, ExpectCT) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitAndEnableFeature(
       TransportSecurityState::kDynamicExpectCTFeature);
@@ -327,11 +352,13 @@
   TransportSecurityState::ExpectCTState expect_ct_state;
   static const char kTestDomain[] = "example.test";
 
-  EXPECT_FALSE(state_.GetDynamicExpectCTState(kTestDomain, &expect_ct_state));
+  EXPECT_FALSE(state_->GetDynamicExpectCTState(
+      kTestDomain, NetworkIsolationKey(), &expect_ct_state));
 
   const base::Time current_time(base::Time::Now());
   const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000);
-  state_.AddExpectCT(kTestDomain, expiry, true /* enforce */, GURL());
+  state_->AddExpectCT(kTestDomain, expiry, true /* enforce */, GURL(),
+                      NetworkIsolationKey());
   std::string serialized;
   EXPECT_TRUE(persister_->SerializeData(&serialized));
   bool data_in_old_format;
@@ -340,19 +367,20 @@
   EXPECT_TRUE(persister_->LoadEntries(serialized, &data_in_old_format));
 
   TransportSecurityState::ExpectCTState new_expect_ct_state;
-  EXPECT_TRUE(
-      state_.GetDynamicExpectCTState(kTestDomain, &new_expect_ct_state));
+  EXPECT_TRUE(state_->GetDynamicExpectCTState(
+      kTestDomain, NetworkIsolationKey(), &new_expect_ct_state));
   EXPECT_TRUE(new_expect_ct_state.enforce);
   EXPECT_TRUE(new_expect_ct_state.report_uri.is_empty());
   EXPECT_EQ(expiry, new_expect_ct_state.expiry);
 
   // Update the state for the domain and check that it is
   // serialized/deserialized correctly.
-  state_.AddExpectCT(kTestDomain, expiry, false /* enforce */, report_uri);
+  state_->AddExpectCT(kTestDomain, expiry, false /* enforce */, report_uri,
+                      NetworkIsolationKey());
   EXPECT_TRUE(persister_->SerializeData(&serialized));
   EXPECT_TRUE(persister_->LoadEntries(serialized, &data_in_old_format));
-  EXPECT_TRUE(
-      state_.GetDynamicExpectCTState(kTestDomain, &new_expect_ct_state));
+  EXPECT_TRUE(state_->GetDynamicExpectCTState(
+      kTestDomain, NetworkIsolationKey(), &new_expect_ct_state));
   EXPECT_FALSE(new_expect_ct_state.enforce);
   EXPECT_EQ(report_uri, new_expect_ct_state.report_uri);
   EXPECT_EQ(expiry, new_expect_ct_state.expiry);
@@ -360,7 +388,7 @@
 
 // Tests that dynamic Expect-CT state is serialized and deserialized correctly
 // when there is also STS data present.
-TEST_F(TransportSecurityPersisterTest, ExpectCTWithSTSDataPresent) {
+TEST_P(TransportSecurityPersisterTest, ExpectCTWithSTSDataPresent) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitAndEnableFeature(
       TransportSecurityState::kDynamicExpectCTFeature);
@@ -368,12 +396,14 @@
   TransportSecurityState::ExpectCTState expect_ct_state;
   static const char kTestDomain[] = "example.test";
 
-  EXPECT_FALSE(state_.GetDynamicExpectCTState(kTestDomain, &expect_ct_state));
+  EXPECT_FALSE(state_->GetDynamicExpectCTState(
+      kTestDomain, NetworkIsolationKey(), &expect_ct_state));
 
   const base::Time current_time(base::Time::Now());
   const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000);
-  state_.AddHSTS(kTestDomain, expiry, false /* include subdomains */);
-  state_.AddExpectCT(kTestDomain, expiry, true /* enforce */, GURL());
+  state_->AddHSTS(kTestDomain, expiry, false /* include subdomains */);
+  state_->AddExpectCT(kTestDomain, expiry, true /* enforce */, GURL(),
+                      NetworkIsolationKey());
 
   std::string serialized;
   EXPECT_TRUE(persister_->SerializeData(&serialized));
@@ -383,21 +413,21 @@
   EXPECT_TRUE(persister_->LoadEntries(serialized, &data_in_old_format));
 
   TransportSecurityState::ExpectCTState new_expect_ct_state;
-  EXPECT_TRUE(
-      state_.GetDynamicExpectCTState(kTestDomain, &new_expect_ct_state));
+  EXPECT_TRUE(state_->GetDynamicExpectCTState(
+      kTestDomain, NetworkIsolationKey(), &new_expect_ct_state));
   EXPECT_TRUE(new_expect_ct_state.enforce);
   EXPECT_TRUE(new_expect_ct_state.report_uri.is_empty());
   EXPECT_EQ(expiry, new_expect_ct_state.expiry);
   // Check that STS state is loaded properly as well.
   TransportSecurityState::STSState sts_state;
-  EXPECT_TRUE(state_.GetDynamicSTSState(kTestDomain, &sts_state));
+  EXPECT_TRUE(state_->GetDynamicSTSState(kTestDomain, &sts_state));
   EXPECT_EQ(sts_state.upgrade_mode,
             TransportSecurityState::STSState::MODE_FORCE_HTTPS);
 }
 
 // Tests that Expect-CT state is not serialized and persisted when the feature
 // is disabled.
-TEST_F(TransportSecurityPersisterTest, ExpectCTDisabled) {
+TEST_P(TransportSecurityPersisterTest, ExpectCTDisabled) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitAndDisableFeature(
       TransportSecurityState::kDynamicExpectCTFeature);
@@ -405,19 +435,184 @@
   TransportSecurityState::ExpectCTState expect_ct_state;
   static const char kTestDomain[] = "example.test";
 
-  EXPECT_FALSE(state_.GetDynamicExpectCTState(kTestDomain, &expect_ct_state));
+  EXPECT_FALSE(state_->GetDynamicExpectCTState(
+      kTestDomain, NetworkIsolationKey(), &expect_ct_state));
 
   const base::Time current_time(base::Time::Now());
   const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000);
-  state_.AddExpectCT(kTestDomain, expiry, true /* enforce */, GURL());
+  state_->AddExpectCT(kTestDomain, expiry, true /* enforce */, GURL(),
+                      NetworkIsolationKey());
   std::string serialized;
   EXPECT_TRUE(persister_->SerializeData(&serialized));
   bool data_in_old_format;
   EXPECT_TRUE(persister_->LoadEntries(serialized, &data_in_old_format));
 
   TransportSecurityState::ExpectCTState new_expect_ct_state;
-  EXPECT_FALSE(
-      state_.GetDynamicExpectCTState(kTestDomain, &new_expect_ct_state));
+  EXPECT_FALSE(state_->GetDynamicExpectCTState(
+      kTestDomain, NetworkIsolationKey(), &new_expect_ct_state));
+}
+
+// Save data with several NetworkIsolationKeys with
+// kPartitionExpectCTStateByNetworkIsolationKey enabled, and then load it with
+// the feature enabled or disabled, based on partition_expect_ct_state().
+TEST_P(TransportSecurityPersisterTest, ExpectCTWithNetworkIsolationKey) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(
+      TransportSecurityState::kDynamicExpectCTFeature);
+
+  const GURL report_uri(kReportUri);
+  static const char kTestDomain[] = "example.test";
+  const url::Origin kOrigin =
+      url::Origin::Create(GURL("https://somewhere.else.test"));
+  const NetworkIsolationKey empty_network_isolation_key;
+  const NetworkIsolationKey network_isolation_key(kOrigin /* top_frame_origin*/,
+                                                  kOrigin /* frame_origin*/);
+  const NetworkIsolationKey transient_network_isolation_key =
+      NetworkIsolationKey::CreateTransient();
+
+  const base::Time current_time(base::Time::Now());
+  const base::Time expiry1 = current_time + base::TimeDelta::FromSeconds(1000);
+  const base::Time expiry2 = current_time + base::TimeDelta::FromSeconds(2000);
+  const base::Time expiry3 = current_time + base::TimeDelta::FromSeconds(3000);
+
+  // Serialize data with kPartitionExpectCTStateByNetworkIsolationKey enabled,
+  // and then revert the feature to its previous value.
+  std::string serialized;
+  {
+    base::test::ScopedFeatureList feature_list2;
+    feature_list2.InitAndEnableFeature(
+        features::kPartitionExpectCTStateByNetworkIsolationKey);
+    TransportSecurityState state2;
+    TransportSecurityPersister persister2(
+        &state2, temp_dir_.GetPath(),
+        std::move(base::ThreadPool::CreateSequencedTaskRunner(
+            {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
+             base::TaskShutdownBehavior::BLOCK_SHUTDOWN})));
+    TransportSecurityState::ExpectCTState expect_ct_state;
+    state2.AddExpectCT(kTestDomain, expiry1, true /* enforce */, GURL(),
+                       empty_network_isolation_key);
+    state2.AddExpectCT(kTestDomain, expiry2, true /* enforce */, GURL(),
+                       network_isolation_key);
+    state2.AddExpectCT(kTestDomain, expiry3, true /* enforce */, GURL(),
+                       transient_network_isolation_key);
+    EXPECT_TRUE(persister2.SerializeData(&serialized));
+
+    EXPECT_TRUE(state2.GetDynamicExpectCTState(
+        kTestDomain, empty_network_isolation_key, &expect_ct_state));
+    EXPECT_TRUE(state2.GetDynamicExpectCTState(
+        kTestDomain, network_isolation_key, &expect_ct_state));
+    EXPECT_TRUE(state2.GetDynamicExpectCTState(
+        kTestDomain, transient_network_isolation_key, &expect_ct_state));
+  }
+
+  bool data_in_old_format;
+  // Load entries into the other persister.
+  EXPECT_TRUE(persister_->LoadEntries(serialized, &data_in_old_format));
+  EXPECT_FALSE(data_in_old_format);
+
+  if (partition_expect_ct_state()) {
+    TransportSecurityState::ExpectCTState new_expect_ct_state;
+    EXPECT_TRUE(state_->GetDynamicExpectCTState(
+        kTestDomain, empty_network_isolation_key, &new_expect_ct_state));
+    EXPECT_TRUE(new_expect_ct_state.enforce);
+    EXPECT_TRUE(new_expect_ct_state.report_uri.is_empty());
+    EXPECT_EQ(expiry1, new_expect_ct_state.expiry);
+
+    EXPECT_TRUE(state_->GetDynamicExpectCTState(
+        kTestDomain, network_isolation_key, &new_expect_ct_state));
+    EXPECT_TRUE(new_expect_ct_state.enforce);
+    EXPECT_TRUE(new_expect_ct_state.report_uri.is_empty());
+    EXPECT_EQ(expiry2, new_expect_ct_state.expiry);
+
+    // The data associated with the transient NetworkIsolationKey should not
+    // have been saved.
+    EXPECT_FALSE(state_->GetDynamicExpectCTState(
+        kTestDomain, transient_network_isolation_key, &new_expect_ct_state));
+  } else {
+    std::set<std::string> expect_ct_saved;
+    TransportSecurityState::ExpectCTStateIterator expect_ct_iter(*state_);
+    ASSERT_TRUE(expect_ct_iter.HasNext());
+    EXPECT_EQ(empty_network_isolation_key,
+              expect_ct_iter.network_isolation_key());
+    EXPECT_TRUE(expect_ct_iter.domain_state().enforce);
+    EXPECT_TRUE(expect_ct_iter.domain_state().report_uri.is_empty());
+    expect_ct_iter.Advance();
+    EXPECT_FALSE(expect_ct_iter.HasNext());
+  }
+}
+
+// Test the case when deserializing a NetworkIsolationKey fails. This happens
+// when data is persisted with kAppendFrameOriginToNetworkIsolationKey, but
+// loaded without it, or vice-versa.
+TEST_P(TransportSecurityPersisterTest,
+       ExpectCTNetworkIsolationKeyDeserializationFails) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(
+      TransportSecurityState::kDynamicExpectCTFeature);
+
+  const GURL report_uri(kReportUri);
+  static const char kTestDomain[] = "example.test";
+  const url::Origin kOrigin =
+      url::Origin::Create(GURL("https://somewhere.else.test"));
+  const NetworkIsolationKey empty_network_isolation_key;
+  const NetworkIsolationKey network_isolation_key(kOrigin /* top_frame_origin*/,
+                                                  kOrigin /* frame_origin*/);
+  const base::Time current_time(base::Time::Now());
+  const base::Time expiry1 = current_time + base::TimeDelta::FromSeconds(1000);
+  const base::Time expiry2 = current_time + base::TimeDelta::FromSeconds(2000);
+
+  // Serialize data with kPartitionExpectCTStateByNetworkIsolationKey and
+  // kAppendFrameOriginToNetworkIsolationKey enabled, and then revert the
+  // features to their previous values.
+  std::string serialized;
+  {
+    base::test::ScopedFeatureList feature_list2;
+    feature_list2.InitWithFeatures(
+        // enabled_features
+        {features::kPartitionExpectCTStateByNetworkIsolationKey,
+         features::kAppendFrameOriginToNetworkIsolationKey},
+        // disabled_features
+        {});
+    TransportSecurityState state2;
+    TransportSecurityPersister persister2(
+        &state2, temp_dir_.GetPath(),
+        std::move(base::ThreadPool::CreateSequencedTaskRunner(
+            {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
+             base::TaskShutdownBehavior::BLOCK_SHUTDOWN})));
+    TransportSecurityState::ExpectCTState expect_ct_state;
+    state2.AddExpectCT(kTestDomain, expiry1, true /* enforce */, GURL(),
+                       empty_network_isolation_key);
+    state2.AddExpectCT(kTestDomain, expiry2, true /* enforce */, GURL(),
+                       network_isolation_key);
+    EXPECT_TRUE(persister2.SerializeData(&serialized));
+
+    EXPECT_TRUE(state2.GetDynamicExpectCTState(
+        kTestDomain, empty_network_isolation_key, &expect_ct_state));
+    EXPECT_TRUE(state2.GetDynamicExpectCTState(
+        kTestDomain, network_isolation_key, &expect_ct_state));
+  }
+
+  base::test::ScopedFeatureList feature_list3;
+  feature_list3.InitAndDisableFeature(
+      features::kAppendFrameOriginToNetworkIsolationKey);
+
+  bool data_in_old_format;
+  // Load entries into the other persister.
+  EXPECT_TRUE(persister_->LoadEntries(serialized, &data_in_old_format));
+  EXPECT_FALSE(data_in_old_format);
+
+  // Regardless of whether kPartitionExpectCTStateByNetworkIsolationKey is
+  // enabled or not, the different kAppendFrameOriginToNetworkIsolationKey state
+  // will cause the entry with a non-empty NetworkIsolationKey to be dropped.
+  std::set<std::string> expect_ct_saved;
+  TransportSecurityState::ExpectCTStateIterator expect_ct_iter(*state_);
+  ASSERT_TRUE(expect_ct_iter.HasNext());
+  EXPECT_EQ(empty_network_isolation_key,
+            expect_ct_iter.network_isolation_key());
+  EXPECT_TRUE(expect_ct_iter.domain_state().enforce);
+  EXPECT_TRUE(expect_ct_iter.domain_state().report_uri.is_empty());
+  expect_ct_iter.Advance();
+  EXPECT_FALSE(expect_ct_iter.HasNext());
 }
 
 }  // namespace