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 }