github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/c-deps/libroach/ccl/key_manager_test.cc (about)

     1  // Copyright 2017 The Cockroach Authors.
     2  //
     3  // Licensed as a CockroachDB Enterprise file under the Cockroach Community
     4  // License (the "License"); you may not use this file except in compliance with
     5  // the License. You may obtain a copy of the License at
     6  //
     7  //     https://github.com/cockroachdb/cockroach/blob/master/licenses/CCL.txt
     8  
     9  #include <rocksdb/env.h>
    10  #include <vector>
    11  #include "../fmt.h"
    12  #include "../testutils.h"
    13  #include "crypto_utils.h"
    14  #include "key_manager.h"
    15  
    16  // A few fixed keys and simple IDs (the first 16 bytes).
    17  // key_file is the file contents for store keys: includes a key ID and key.
    18  // key is the key.
    19  // key_id is the ID (Hex of the first part of key_file)
    20  const std::string key_file_128 = "111111111111111111111111111111111234567890123456";
    21  const std::string key_file_192 = "22222222222222222222222222222222123456789012345678901234";
    22  const std::string key_file_256 = "3333333333333333333333333333333312345678901234567890123456789012";
    23  const std::string key_128 = "1234567890123456";
    24  const std::string key_192 = "123456789012345678901234";
    25  const std::string key_256 = "12345678901234567890123456789012";
    26  
    27  // Hex of the binary value of the first kKeyIDLength of key_file.
    28  const std::string key_id_128 = "3131313131313131313131313131313131313131313131313131313131313131";
    29  const std::string key_id_192 = "3232323232323232323232323232323232323232323232323232323232323232";
    30  const std::string key_id_256 = "3333333333333333333333333333333333333333333333333333333333333333";
    31  
    32  TEST(FileKeyManager, ReadKeyFiles) {
    33    std::unique_ptr<rocksdb::Env> env(rocksdb::NewMemEnv(rocksdb::Env::Default()));
    34  
    35    // Write a few keys.
    36    struct KeyFiles {
    37      std::string filename;
    38      std::string contents;
    39    };
    40  
    41    std::vector<KeyFiles> keys = {
    42        {"empty.key", ""},
    43        {"noid_8.key", "12345678"},
    44        {"noid_16.key", "1234567890123456"},
    45        {"noid_24.key", "123456789012345678901234"},
    46        {"noid_32.key", "12345678901234567890123456789012"},
    47        {"16.key", key_file_128},
    48        {"24.key", key_file_192},
    49        {"32.key", key_file_256},
    50    };
    51    for (auto k : keys) {
    52      ASSERT_OK(rocksdb::WriteStringToFile(env.get(), k.contents, k.filename));
    53    }
    54  
    55    struct TestCase {
    56      std::string active_file;
    57      std::string old_file;
    58      std::string error;
    59    };
    60  
    61    std::vector<TestCase> test_cases = {
    62        {"", "", ": File not found"},
    63        {"missing_new.key", "missing_old.key", "missing_new.key: File not found"},
    64        {"plain", "missing_old.key", "missing_old.key: File not found"},
    65        {"plain", "plain", ""},
    66        {"empty.key", "plain",
    67         R"(file empty.key is 0 bytes long, it must be <key ID length \(32\)> \+ <key size \(16, 24, or 32\)> long)"},
    68        {"noid_8.key", "plain", "file noid_8.key is 8 bytes long, it must be .*"},
    69        {"noid_16.key", "plain", "file noid_16.key is 16 bytes long, it must be .*"},
    70        {"noid_24.key", "plain", "file noid_24.key is 24 bytes long, it must be .*"},
    71        {"noid_32.key", "plain", "file noid_32.key is 32 bytes long, it must be .*"},
    72        {"16.key", "plain", ""},
    73        {"24.key", "plain", ""},
    74        {"32.key", "plain", ""},
    75        {"16.key", "noid_8.key", "file noid_8.key is 8 bytes long, it must be .*"},
    76        {"16.key", "32.key", ""},
    77    };
    78  
    79    int test_num = 0;
    80    for (auto t : test_cases) {
    81      SCOPED_TRACE(fmt::StringPrintf("Testing #%d", test_num++));
    82  
    83      FileKeyManager fkm(env.get(), nullptr, t.active_file, t.old_file);
    84      auto status = fkm.LoadKeys();
    85      EXPECT_ERR(status, t.error);
    86    }
    87  }
    88  
    89  struct testKey {
    90    std::string id;
    91    std::string key;
    92    enginepbccl::EncryptionType type;
    93    int64_t approximate_timestamp;  // We check that the creation time is within 5s of this timestamp.
    94    std::string source;
    95    bool was_exposed;
    96    std::string parent_key_id;
    97  
    98    // Constructors with various fields specified.
    99    testKey() : testKey("") {}
   100    // Copy constructor.
   101    testKey(const testKey& k)
   102        : testKey(k.id, k.key, k.type, k.approximate_timestamp, k.source, k.was_exposed,
   103                  k.parent_key_id) {}
   104    // Key ID only (to test key existence).
   105    testKey(std::string id) : testKey(id, "", enginepbccl::Plaintext, 0, "") {}
   106    testKey(std::string id, std::string key, enginepbccl::EncryptionType type, int64_t timestamp,
   107            std::string source)
   108        : testKey(id, key, type, timestamp, source, false, "") {}
   109    testKey(std::string id, std::string key, enginepbccl::EncryptionType type, int64_t timestamp,
   110            std::string source, bool exposed, std::string parent)
   111        : id(id),
   112          key(key),
   113          type(type),
   114          approximate_timestamp(timestamp),
   115          source(source),
   116          was_exposed(exposed),
   117          parent_key_id(parent) {}
   118  };
   119  
   120  enginepbccl::KeyInfo keyInfoFromTestKey(const testKey& k) {
   121    enginepbccl::KeyInfo ki;
   122    ki.set_encryption_type(k.type);
   123    ki.set_key_id(k.id);
   124    ki.set_creation_time(k.approximate_timestamp);
   125    ki.set_source(k.source);
   126    return ki;
   127  }
   128  
   129  enginepbccl::SecretKey secretKeyFromTestKey(const testKey& k) {
   130    enginepbccl::SecretKey sk;
   131    sk.set_key(k.key);
   132    sk.set_allocated_info(new enginepbccl::KeyInfo(keyInfoFromTestKey(k)));
   133    return sk;
   134  }
   135  
   136  rocksdb::Status compareNonRandomKeyInfo(const enginepbccl::KeyInfo& actual,
   137                                          const testKey& expected) {
   138    if (actual.encryption_type() != expected.type) {
   139      return rocksdb::Status::InvalidArgument(fmt::StringPrintf(
   140          "actual type %d does not match expected type %d", actual.encryption_type(), expected.type));
   141    }
   142  
   143    auto diff = actual.creation_time() - expected.approximate_timestamp;
   144    if (diff > 5 || diff < -5) {
   145      return rocksdb::Status::InvalidArgument(fmt::StringPrintf(
   146          "actual creation time %" PRId64 " does not match expected timestamp %" PRId64,
   147          actual.creation_time(), expected.approximate_timestamp));
   148    }
   149  
   150    if (actual.source() != expected.source) {
   151      return rocksdb::Status::InvalidArgument(
   152          fmt::StringPrintf("actual source \"%s\" does not match expected source \"%s\"",
   153                            actual.source().c_str(), expected.source.c_str()));
   154    }
   155  
   156    if (actual.was_exposed() != expected.was_exposed) {
   157      return rocksdb::Status::InvalidArgument(
   158          fmt::StringPrintf("actual was_exposed %d does not match expected was_exposed %d",
   159                            actual.was_exposed(), expected.was_exposed));
   160    }
   161    return rocksdb::Status::OK();
   162  }
   163  
   164  rocksdb::Status compareKeyInfo(const enginepbccl::KeyInfo& actual, const testKey& expected) {
   165    auto status = compareNonRandomKeyInfo(actual, expected);
   166    if (!status.ok()) {
   167      return status;
   168    }
   169  
   170    if (actual.key_id() != expected.id) {
   171      return rocksdb::Status::InvalidArgument(
   172          fmt::StringPrintf("actual key_id \"%s\" does not match expected key_id \"%s\"",
   173                            actual.key_id().c_str(), expected.id.c_str()));
   174    }
   175    return rocksdb::Status::OK();
   176  }
   177  
   178  rocksdb::Status compareKey(const enginepbccl::SecretKey& actual, const testKey& expected) {
   179    if (actual.key() != expected.key) {
   180      return rocksdb::Status::InvalidArgument(
   181          fmt::StringPrintf("actual key \"%s\" does not match expected key \"%s\"",
   182                            actual.key().c_str(), expected.key.c_str()));
   183    }
   184    return compareKeyInfo(actual.info(), expected);
   185  }
   186  
   187  void getInfoFromVec(std::vector<testKey> infos, const std::string& id, testKey* out) {
   188    for (auto i : infos) {
   189      if (i.id == id) {
   190        *out = i;
   191        return;
   192      }
   193    }
   194    FAIL() << "key id " << id << " not found in info vector";
   195  }
   196  
   197  TEST(FileKeyManager, BuildKeyFromFile) {
   198    std::unique_ptr<rocksdb::Env> env(rocksdb::NewMemEnv(rocksdb::Env::Default()));
   199  
   200    // Write a few keys.
   201    ASSERT_OK(rocksdb::WriteStringToFile(env.get(), key_file_128, "16.key"));
   202    ASSERT_OK(rocksdb::WriteStringToFile(env.get(), key_file_192, "24.key"));
   203    ASSERT_OK(rocksdb::WriteStringToFile(env.get(), key_file_256, "32.key"));
   204  
   205    int64_t now;
   206    ASSERT_OK(env->GetCurrentTime(&now));
   207    struct TestCase {
   208      std::string active_file;
   209      std::string old_file;
   210      std::string active_id;
   211      std::vector<testKey> keys;
   212    };
   213  
   214    std::vector<TestCase> test_cases = {
   215        {"plain", "plain", "plain", {testKey("plain", "", enginepbccl::Plaintext, now, "plain")}},
   216        {"16.key",
   217         "32.key",
   218         key_id_128,
   219         {testKey(key_id_128, key_128, enginepbccl::AES128_CTR, now, "16.key"),
   220          testKey(key_id_256, key_256, enginepbccl::AES256_CTR, now, "32.key")}},
   221        {
   222            "24.key",
   223            "plain",
   224            key_id_192,
   225            {testKey(key_id_192, key_192, enginepbccl::AES192_CTR, now, "24.key"),
   226             testKey("plain", "", enginepbccl::Plaintext, now, "plain")},
   227        },
   228    };
   229  
   230    int test_num = 0;
   231    for (auto t : test_cases) {
   232      SCOPED_TRACE(fmt::StringPrintf("Testing #%d", test_num++));
   233  
   234      FileKeyManager fkm(env.get(), nullptr, t.active_file, t.old_file);
   235      ASSERT_OK(fkm.LoadKeys());
   236  
   237      testKey ki;
   238      getInfoFromVec(t.keys, t.active_id, &ki);
   239  
   240      auto secret_key = fkm.CurrentKey();
   241      EXPECT_EQ(secret_key->key(), ki.key);
   242      EXPECT_OK(compareKeyInfo(secret_key->info(), ki));
   243  
   244      auto key_info = fkm.CurrentKeyInfo();
   245      EXPECT_OK(compareKeyInfo(*key_info, ki));
   246  
   247      for (auto ki_iter : t.keys) {
   248        if (ki_iter.id == "") {
   249          continue;
   250        }
   251        secret_key = fkm.GetKey(ki_iter.id);
   252        ASSERT_NE(secret_key, nullptr);
   253        EXPECT_EQ(secret_key->key(), ki_iter.key);
   254        EXPECT_OK(compareKeyInfo(secret_key->info(), ki_iter));
   255  
   256        key_info = fkm.GetKeyInfo(ki_iter.id);
   257        ASSERT_NE(key_info, nullptr);
   258        EXPECT_OK(compareKeyInfo(*key_info, ki_iter));
   259      }
   260    }
   261  }
   262  
   263  TEST(DataKeyManager, LoadKeys) {
   264    std::unique_ptr<rocksdb::Env> env(rocksdb::NewMemEnv(rocksdb::Env::Default()));
   265    const std::string registry_path = "/" + kKeyRegistryFilename;
   266  
   267    // Test a missing file first.
   268    {
   269      DataKeyManager dkm(env.get(), nullptr, "", 0, false /* read-only */);
   270      EXPECT_OK(dkm.LoadKeys());
   271      ASSERT_EQ(dkm.CurrentKey(), nullptr);
   272    }
   273  
   274    // Now a file with random data.
   275    {
   276      ASSERT_OK(rocksdb::WriteStringToFile(env.get(), "blah blah", registry_path));
   277      DataKeyManager dkm(env.get(), nullptr, "", 0, false /* read-only */);
   278      EXPECT_ERR(dkm.LoadKeys(), "failed to parse key registry " + registry_path);
   279      ASSERT_OK(env->DeleteFile(registry_path));
   280    }
   281  
   282    // Empty file.
   283    {
   284      ASSERT_OK(rocksdb::WriteStringToFile(env.get(), "", registry_path));
   285      DataKeyManager dkm(env.get(), nullptr, "", 0, false /* read-only */);
   286      EXPECT_OK(dkm.LoadKeys());
   287      ASSERT_OK(env->DeleteFile(registry_path));
   288    }
   289  
   290    // Empty protobuf.
   291    {
   292      enginepbccl::DataKeysRegistry registry;
   293      std::string contents;
   294      ASSERT_TRUE(registry.SerializeToString(&contents));
   295      ASSERT_OK(rocksdb::WriteStringToFile(env.get(), contents, registry_path));
   296      DataKeyManager dkm(env.get(), nullptr, "", 0, false /* read-only */);
   297      EXPECT_OK(dkm.LoadKeys());
   298      ASSERT_OK(env->DeleteFile(registry_path));
   299    }
   300  
   301    // Active store key not found.
   302    {
   303      enginepbccl::DataKeysRegistry registry;
   304      registry.set_active_store_key_id("foobar");
   305  
   306      std::string contents;
   307      ASSERT_TRUE(registry.SerializeToString(&contents));
   308      ASSERT_OK(rocksdb::WriteStringToFile(env.get(), contents, registry_path));
   309  
   310      DataKeyManager dkm(env.get(), nullptr, "", 0, false /* read-only */);
   311      EXPECT_ERR(dkm.LoadKeys(), "active store key foobar not found");
   312      ASSERT_OK(env->DeleteFile(registry_path));
   313    }
   314  
   315    // Active data key not found.
   316    {
   317      enginepbccl::DataKeysRegistry registry;
   318      registry.set_active_data_key_id("foobar");
   319  
   320      std::string contents;
   321      ASSERT_TRUE(registry.SerializeToString(&contents));
   322      ASSERT_OK(rocksdb::WriteStringToFile(env.get(), contents, registry_path));
   323  
   324      DataKeyManager dkm(env.get(), nullptr, "", 0, false /* read-only */);
   325      EXPECT_ERR(dkm.LoadKeys(), "active data key foobar not found");
   326      ASSERT_OK(env->DeleteFile(registry_path));
   327    }
   328  
   329    // Both active keys exist.
   330    {
   331      enginepbccl::DataKeysRegistry registry;
   332      (*registry.mutable_store_keys())["foo"] = keyInfoFromTestKey(testKey("foo"));
   333      (*registry.mutable_store_keys())["bar"] = keyInfoFromTestKey(testKey("bar"));
   334      registry.set_active_store_key_id("foo");
   335  
   336      testKey foo2 = testKey("foo2");
   337      (*registry.mutable_data_keys())["foo2"] = secretKeyFromTestKey(foo2);
   338      testKey bar2 = testKey("bar2");
   339      (*registry.mutable_data_keys())["bar2"] = secretKeyFromTestKey(bar2);
   340      registry.set_active_data_key_id("bar2");
   341  
   342      std::string contents;
   343      ASSERT_TRUE(registry.SerializeToString(&contents));
   344      ASSERT_OK(rocksdb::WriteStringToFile(env.get(), contents, registry_path));
   345      DataKeyManager dkm(env.get(), nullptr, "", 0, false /* read-only */);
   346      EXPECT_OK(dkm.LoadKeys());
   347  
   348      auto k = dkm.CurrentKey();
   349      ASSERT_NE(k, nullptr);
   350      EXPECT_OK(compareKeyInfo(k->info(), bar2));
   351  
   352      auto ki = dkm.CurrentKeyInfo();
   353      ASSERT_NE(ki, nullptr);
   354      EXPECT_OK(compareKeyInfo(*ki, bar2));
   355  
   356      k = dkm.GetKey("foo2");
   357      ASSERT_NE(k, nullptr);
   358      EXPECT_OK(compareKeyInfo(k->info(), foo2));
   359  
   360      ki = dkm.GetKeyInfo("foo2");
   361      ASSERT_NE(ki, nullptr);
   362      EXPECT_OK(compareKeyInfo(*ki, foo2));
   363  
   364      ASSERT_OK(env->DeleteFile(registry_path));
   365    }
   366  }
   367  
   368  TEST(DataKeyManager, KeyFromKeyInfo) {
   369    std::unique_ptr<rocksdb::Env> env(rocksdb::NewMemEnv(rocksdb::Env::Default()));
   370  
   371    int64_t now;
   372    ASSERT_OK(env->GetCurrentTime(&now));
   373  
   374    struct TestCase {
   375      testKey store_info;
   376      std::string error;
   377      testKey new_key;
   378    };
   379  
   380    std::vector<TestCase> test_cases = {
   381        {testKey("bad", "", enginepbccl::EncryptionType_INT_MIN_SENTINEL_DO_NOT_USE_, 0, ""),
   382         "unknown encryption type .* for key ID bad",
   383         {}},
   384        {testKey("plain", "", enginepbccl::Plaintext, now, "plain"), "",
   385         testKey("plain", "", enginepbccl::Plaintext, now, "data key manager", true, "plain")},
   386        {testKey(key_id_128, "", enginepbccl::AES128_CTR, now - 86400, "128.key"), "",
   387         testKey("someid", "somekey", enginepbccl::AES128_CTR, now, "data key manager", false,
   388                 key_id_128)},
   389        {testKey(key_id_192, "", enginepbccl::AES192_CTR, now + 86400, "192.key"), "",
   390         testKey("someid", "somekey", enginepbccl::AES192_CTR, now, "data key manager", false,
   391                 key_id_192)},
   392        {testKey(key_id_256, "", enginepbccl::AES256_CTR, 0, "256.key"), "",
   393         testKey("someid", "somekey", enginepbccl::AES256_CTR, now, "data key manager", false,
   394                 key_id_256)},
   395    };
   396  
   397    int test_num = 0;
   398    for (auto t : test_cases) {
   399      SCOPED_TRACE(fmt::StringPrintf("Testing #%d", test_num++));
   400  
   401      enginepbccl::SecretKey new_key;
   402      auto status =
   403          KeyManagerUtils::KeyFromKeyInfo(env.get(), keyInfoFromTestKey(t.store_info), &new_key);
   404      EXPECT_ERR(status, t.error);
   405      if (!status.ok()) {
   406        continue;
   407      }
   408  
   409      // Check only the non-random bits (everything but key and ID).
   410      EXPECT_OK(compareNonRandomKeyInfo(new_key.info(), t.new_key));
   411  
   412      size_t expected_length;
   413      switch (t.new_key.type) {
   414      case enginepbccl::Plaintext:
   415        expected_length = 0;
   416        break;
   417      case enginepbccl::AES128_CTR:
   418        expected_length = 16;
   419        break;
   420      case enginepbccl::AES192_CTR:
   421        expected_length = 24;
   422        break;
   423      case enginepbccl::AES256_CTR:
   424        expected_length = 32;
   425        break;
   426      default:
   427        FAIL() << "unknown encryption type: " << t.new_key.type;
   428      }
   429  
   430      EXPECT_EQ(new_key.key().size(), expected_length);
   431      if (t.new_key.type == enginepbccl::Plaintext) {
   432        EXPECT_EQ(new_key.key(), "");
   433      } else {
   434        EXPECT_EQ(new_key.info().key_id().size(), kKeyIDLength * 2 /* Hex doubles the size) */);
   435      }
   436    }
   437  }
   438  
   439  TEST(DataKeyManager, SetStoreKey) {
   440    std::unique_ptr<rocksdb::Env> env(rocksdb::NewMemEnv(rocksdb::Env::Default()));
   441  
   442    int64_t now;
   443    ASSERT_OK(env->GetCurrentTime(&now));
   444  
   445    struct TestCase {
   446      testKey store_info;
   447      std::string error;
   448      testKey active_key;
   449    };
   450  
   451    // Test cases are incremental: the key manager is not reset for each key.
   452    // If error is empty, the active data key should match the "active key".
   453    // The active key is then added to the list of keys.
   454    std::vector<testKey> active_keys;
   455    std::vector<TestCase> test_cases = {
   456        {
   457            testKey("plain", "", enginepbccl::Plaintext, now, "plain"),
   458            "",
   459            testKey("plain", "", enginepbccl::Plaintext, now, "data key manager", true, "plain"),
   460        },
   461        {
   462            testKey(key_id_128, "", enginepbccl::AES128_CTR, now - 86400, "128.key"),
   463            "",
   464            testKey(key_id_128, "", enginepbccl::AES128_CTR, now, "data key manager", false,
   465                    key_id_128),
   466        },
   467        {
   468            // Setting the same store key does nothing.
   469            testKey(key_id_128, "", enginepbccl::AES128_CTR, now - 86400, "128.key"),
   470            "",
   471            // We add it again because we don't have a "skip" thing. It's find if it's there twice.
   472            testKey(key_id_128, "", enginepbccl::AES128_CTR, now, "data key manager", false,
   473                    key_id_128),
   474        },
   475        {
   476            testKey(key_id_256, "", enginepbccl::AES256_CTR, now - 86400, "256.key"),
   477            "",
   478            testKey(key_id_256, "", enginepbccl::AES256_CTR, now, "data key manager", false,
   479                    key_id_256),
   480        },
   481        {
   482            testKey(key_id_128, "", enginepbccl::AES128_CTR, now - 86400, "128.key"),
   483            fmt::StringPrintf("new active store key ID %s already exists as an inactive key. This is "
   484                              "really dangerous.",
   485                              key_id_128.c_str()),
   486            {},
   487        },
   488        {
   489            // Switch to plain: this marks all keys as exposed.
   490            testKey("plain", "", enginepbccl::Plaintext, now, "plain"),
   491            "",
   492            testKey("plain", "", enginepbccl::Plaintext, now, "data key manager", true, "plain"),
   493        }};
   494  
   495    DataKeyManager dkm(env.get(), nullptr, "", 0, false /* read-only */);
   496    ASSERT_OK(dkm.LoadKeys());
   497  
   498    int test_num = 0;
   499    for (auto t : test_cases) {
   500      SCOPED_TRACE(fmt::StringPrintf("Testing #%d", test_num++));
   501  
   502      // Set new active store key.
   503      auto store_info = std::unique_ptr<enginepbccl::KeyInfo>(
   504          new enginepbccl::KeyInfo(keyInfoFromTestKey(t.store_info)));
   505      auto status = dkm.SetActiveStoreKeyInfo(std::move(store_info));
   506      EXPECT_ERR(status, t.error);
   507      if (status.ok()) {
   508        // New key generated. Check active data key.
   509        auto active_info = dkm.CurrentKey();
   510        ASSERT_NE(active_info, nullptr);
   511        EXPECT_OK(compareNonRandomKeyInfo(active_info->info(), t.active_key));
   512  
   513        if (t.store_info.type == enginepbccl::Plaintext) {
   514          // Plaintext store info: mark all existing keys as exposed.
   515          for (auto ki_iter = active_keys.begin(); ki_iter != active_keys.end(); ++ki_iter) {
   516            ki_iter->was_exposed = true;
   517          }
   518        }
   519  
   520        // Insert new key with filled-in ID and key.
   521        auto new_key = testKey(t.active_key);
   522        new_key.id = active_info->info().key_id();
   523        new_key.key = active_info->key();
   524        active_keys.push_back(new_key);
   525      }
   526  
   527      // Check all expected keys.
   528      for (auto ki_iter : active_keys) {
   529        auto ki = dkm.GetKey(ki_iter.id);
   530        ASSERT_NE(ki, nullptr);
   531        EXPECT_OK(compareKey(*ki, ki_iter));
   532      }
   533  
   534      // Initialize a new data key manager to load the file.
   535      DataKeyManager tmp_dkm(env.get(), nullptr, "", 0, false /* read-only */);
   536      ASSERT_OK(tmp_dkm.LoadKeys());
   537  
   538      if (status.ok()) {
   539        // Check active data key.
   540        auto active_info = tmp_dkm.CurrentKey();
   541        ASSERT_NE(active_info, nullptr);
   542        EXPECT_OK(compareNonRandomKeyInfo(active_info->info(), t.active_key));
   543      }
   544  
   545      // Check all expected keys.
   546      for (auto ki_iter : active_keys) {
   547        auto ki = tmp_dkm.GetKey(ki_iter.id);
   548        ASSERT_NE(ki, nullptr);
   549        EXPECT_OK(compareKey(*ki, ki_iter));
   550      }
   551    }
   552  }
   553  
   554  TEST(DataKeyManager, RotateKeyAtStartup) {
   555    // MemEnv returns a MockEnv, but use `new mockEnv` to access FakeSleepForMicroseconds.
   556    // We need to wrap it around a memenv for memory files.
   557    std::unique_ptr<rocksdb::Env> memenv(rocksdb::NewMemEnv(rocksdb::Env::Default()));
   558    std::unique_ptr<testutils::FakeTimeEnv> env(new testutils::FakeTimeEnv(memenv.get()));
   559  
   560    struct TestCase {
   561      testKey store_info;    // Active store key info.
   562      int64_t current_time;  // We update the fake env time before the call to SetActiveStoreKeyInfo.
   563      testKey active_key;    // We call compareNonRandomKeyInfo against the active data key.
   564    };
   565  
   566    // Test cases are incremental: the key manager is not reset for each key.
   567    std::vector<TestCase> test_cases = {
   568        {
   569            testKey("plain", "", enginepbccl::Plaintext, 0, "plain"),
   570            0,
   571            testKey("plain", "", enginepbccl::Plaintext, 0, "data key manager", true, "plain"),
   572        },
   573        {
   574            // Plain keys are not rotated.
   575            testKey("plain", "", enginepbccl::Plaintext, 0, "plain"),
   576            20,
   577            testKey("plain", "", enginepbccl::Plaintext, 0, "data key manager", true, "plain"),
   578        },
   579        {
   580            // New key on store key change.
   581            testKey(key_id_128, "", enginepbccl::AES128_CTR, 0, "128.key"),
   582            20,
   583            testKey("not checked", "not checked", enginepbccl::AES128_CTR, 20, "data key manager",
   584                    false, key_id_128),
   585        },
   586        {
   587            // Less than 10 seconds: no change.
   588            testKey(key_id_128, "", enginepbccl::AES128_CTR, 0, "128.key"),
   589            29,
   590            testKey("not checked", "not checked", enginepbccl::AES128_CTR, 20, "data key manager",
   591                    false, key_id_128),
   592        },
   593        {
   594            // Exactly 10 seconds: new key.
   595            testKey(key_id_128, "", enginepbccl::AES128_CTR, 0, "128.key"),
   596            30,
   597            testKey("not checked", "not checked", enginepbccl::AES128_CTR, 30, "data key manager",
   598                    false, key_id_128),
   599        },
   600    };
   601  
   602    DataKeyManager dkm(env.get(), nullptr, "", 10 /* 10 second rotation period */,
   603                       false /* read-only */);
   604    ASSERT_OK(dkm.LoadKeys());
   605  
   606    int test_num = 0;
   607    for (auto t : test_cases) {
   608      SCOPED_TRACE(fmt::StringPrintf("Testing #%d", test_num++));
   609      env->SetCurrentTime(t.current_time);
   610  
   611      // Set new active store key.
   612      auto store_info = std::unique_ptr<enginepbccl::KeyInfo>(
   613          new enginepbccl::KeyInfo(keyInfoFromTestKey(t.store_info)));
   614      auto status = dkm.SetActiveStoreKeyInfo(std::move(store_info));
   615      ASSERT_OK(status);
   616  
   617      auto active_info = dkm.CurrentKey();
   618      ASSERT_NE(active_info, nullptr);
   619      EXPECT_OK(compareNonRandomKeyInfo(active_info->info(), t.active_key));
   620    }
   621  
   622    {
   623      // Now try a read-only data key manager.
   624      DataKeyManager ro_dkm(env.get(), nullptr, "", 10, true);
   625      ASSERT_OK(ro_dkm.LoadKeys());
   626      // Verify that the key matches the last test case.
   627      auto tc = test_cases.back();
   628      auto active_info = ro_dkm.CurrentKey();
   629      ASSERT_NE(active_info, nullptr);
   630      EXPECT_OK(compareNonRandomKeyInfo(active_info->info(), tc.active_key));
   631  
   632      // Increase time, check rotation failure, and check key again.
   633      env->SetCurrentTime(tc.current_time + 100);
   634      auto store_info = std::unique_ptr<enginepbccl::KeyInfo>(
   635          new enginepbccl::KeyInfo(keyInfoFromTestKey(tc.store_info)));
   636      EXPECT_ERR(ro_dkm.SetActiveStoreKeyInfo(std::move(store_info)),
   637                 "key manager is read-only, keys cannot be rotated");
   638      active_info = ro_dkm.CurrentKey();
   639      ASSERT_NE(active_info, nullptr);
   640      EXPECT_OK(compareNonRandomKeyInfo(active_info->info(), tc.active_key));
   641    }
   642  }
   643  
   644  TEST(DataKeyManager, RotateKeyWhileRunning) {
   645    // MemEnv returns a MockEnv, but use `new mockEnv` to access FakeSleepForMicroseconds.
   646    // We need to wrap it around a memenv for memory files.
   647    std::unique_ptr<rocksdb::Env> memenv(rocksdb::NewMemEnv(rocksdb::Env::Default()));
   648    std::unique_ptr<testutils::FakeTimeEnv> env(new testutils::FakeTimeEnv(memenv.get()));
   649  
   650    DataKeyManager dkm(env.get(), nullptr, "", 10 /* 10 second rotation period */,
   651                       false /* read-only */);
   652    ASSERT_OK(dkm.LoadKeys());
   653  
   654    // Set new active store key.
   655    auto plain_key = testKey("plain", "", enginepbccl::Plaintext, 0, "plain");
   656    auto plain_info = std::unique_ptr<enginepbccl::KeyInfo>(
   657        new enginepbccl::KeyInfo(keyInfoFromTestKey(plain_key)));
   658    ASSERT_OK(dkm.SetActiveStoreKeyInfo(std::move(plain_info)));
   659  
   660    auto key1 = dkm.CurrentKeyInfo();
   661    EXPECT_EQ(plain_key.id, key1->key_id());
   662  
   663    // Try as we might, we don't rotate plaintext.
   664    env->IncCurrentTime(60);
   665    auto key2 = dkm.CurrentKeyInfo();
   666    EXPECT_EQ(key1->key_id(), key2->key_id());
   667    EXPECT_EQ(key1->creation_time(), key2->creation_time());
   668  
   669    // Switch to actual encryption.
   670    auto aes_key = testKey(key_id_128, "", enginepbccl::AES128_CTR, 0, "128.key");
   671    auto aes_info =
   672        std::unique_ptr<enginepbccl::KeyInfo>(new enginepbccl::KeyInfo(keyInfoFromTestKey(aes_key)));
   673    ASSERT_OK(dkm.SetActiveStoreKeyInfo(std::move(aes_info)));
   674  
   675    auto aes_key1 = dkm.CurrentKeyInfo();
   676    EXPECT_EQ(aes_key.id, aes_key1->parent_key_id());
   677    EXPECT_EQ(aes_key1->encryption_type(), enginepbccl::AES128_CTR);
   678  
   679    // Let's grab the key a few times.
   680    for (int i = 0; i < 9; i++) {
   681      env->IncCurrentTime(1);
   682      auto aes_key2 = dkm.CurrentKeyInfo();
   683      EXPECT_EQ(aes_key1->key_id(), aes_key2->key_id());
   684      EXPECT_EQ(aes_key1->creation_time(), aes_key2->creation_time());
   685      EXPECT_EQ(aes_key2->encryption_type(), enginepbccl::AES128_CTR);
   686    }
   687  
   688    // Go over the 10s lifetime.
   689    env->IncCurrentTime(2);
   690    auto aes_key2 = dkm.CurrentKeyInfo();
   691    EXPECT_NE(aes_key1->key_id(), aes_key2->key_id());
   692    EXPECT_GT(aes_key2->creation_time(), aes_key1->creation_time());
   693    EXPECT_EQ(aes_key2->encryption_type(), enginepbccl::AES128_CTR);
   694  
   695    // And again.
   696    env->IncCurrentTime(11);
   697    auto aes_key3 = dkm.CurrentKeyInfo();
   698    EXPECT_NE(aes_key3->key_id(), aes_key2->key_id());
   699    EXPECT_NE(aes_key3->key_id(), aes_key1->key_id());
   700    EXPECT_GT(aes_key3->creation_time(), aes_key2->creation_time());
   701    EXPECT_EQ(aes_key3->encryption_type(), enginepbccl::AES128_CTR);
   702  }