github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/c-deps/libroach/ccl/key_manager.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 "key_manager.h" 10 #include "../fmt.h" 11 #include "../utils.h" 12 #include "crypto_utils.h" 13 14 static const std::string kFilenamePlain = "plain"; 15 16 namespace KeyManagerUtils { 17 18 rocksdb::Status KeyFromFile(rocksdb::Env* env, const std::string& path, 19 enginepbccl::SecretKey* key) { 20 int64_t now; 21 auto status = env->GetCurrentTime(&now); 22 if (!status.ok()) { 23 return status; 24 } 25 26 auto info = key->mutable_info(); 27 if (path == kFilenamePlain) { 28 key->set_key(""); 29 info->set_encryption_type(enginepbccl::Plaintext); 30 info->set_key_id(kPlainKeyID); 31 info->set_creation_time(now); 32 info->set_source(kFilenamePlain); 33 return rocksdb::Status::OK(); 34 } 35 36 // Real file, try to read it. 37 std::string contents; 38 status = rocksdb::ReadFileToString(env, path, &contents); 39 if (!status.ok()) { 40 return status; 41 } 42 43 // Check that the length is valid for AES. 44 size_t key_length = contents.size() - kKeyIDLength; 45 switch (key_length) { 46 case 16: 47 info->set_encryption_type(enginepbccl::AES128_CTR); 48 break; 49 case 24: 50 info->set_encryption_type(enginepbccl::AES192_CTR); 51 break; 52 case 32: 53 info->set_encryption_type(enginepbccl::AES256_CTR); 54 break; 55 default: 56 return rocksdb::Status::InvalidArgument( 57 fmt::StringPrintf("file %s is %zu bytes long, it must be <key ID length (%zu)> + <key " 58 "size (16, 24, or 32)> long", 59 path.c_str(), contents.size(), kKeyIDLength)); 60 } 61 62 // Fill in the key and ID: first kKeyIDLength are the ID, the rest are the key. 63 info->set_key_id(HexString(contents.substr(0, kKeyIDLength))); 64 key->set_key(contents.substr(kKeyIDLength, contents.size() - kKeyIDLength)); 65 info->set_creation_time(now); 66 info->set_source(path); 67 68 return rocksdb::Status::OK(); 69 } 70 71 rocksdb::Status KeyFromKeyInfo(rocksdb::Env* env, const enginepbccl::KeyInfo& store_info, 72 enginepbccl::SecretKey* key) { 73 int64_t now; 74 auto status = env->GetCurrentTime(&now); 75 if (!status.ok()) { 76 return status; 77 } 78 79 auto info = key->mutable_info(); 80 info->set_encryption_type(store_info.encryption_type()); 81 info->set_creation_time(now); 82 info->set_source("data key manager"); 83 info->set_parent_key_id(store_info.key_id()); 84 85 if (store_info.encryption_type() == enginepbccl::Plaintext) { 86 key->set_key(""); 87 info->set_key_id(kPlainKeyID); 88 info->set_was_exposed(true); 89 return rocksdb::Status::OK(); 90 } 91 92 // AES encryption. 93 size_t length; 94 switch (store_info.encryption_type()) { 95 case enginepbccl::AES128_CTR: 96 length = 16; 97 break; 98 case enginepbccl::AES192_CTR: 99 length = 24; 100 break; 101 case enginepbccl::AES256_CTR: 102 length = 32; 103 break; 104 default: 105 return rocksdb::Status::InvalidArgument( 106 fmt::StringPrintf("unknown encryption type %d for key ID %s", store_info.encryption_type(), 107 store_info.key_id().c_str())); 108 } 109 key->set_key(RandomBytes(length)); 110 // Assign a random ID to the key. 111 info->set_key_id(HexString(RandomBytes(kKeyIDLength))); 112 info->set_was_exposed(false); // For completeness only. 113 return rocksdb::Status::OK(); 114 } 115 116 rocksdb::Status ValidateRegistry(enginepbccl::DataKeysRegistry* registry) { 117 // Make sure active keys exist if set. 118 if (registry->active_store_key_id() != "" && 119 registry->store_keys().find(registry->active_store_key_id()) == 120 registry->store_keys().cend()) { 121 return rocksdb::Status::InvalidArgument(fmt::StringPrintf( 122 "active store key %s not found", registry->active_store_key_id().c_str())); 123 } 124 125 if (registry->active_data_key_id() != "" && 126 registry->data_keys().find(registry->active_data_key_id()) == registry->data_keys().cend()) { 127 return rocksdb::Status::InvalidArgument( 128 fmt::StringPrintf("active data key %s not found", registry->active_data_key_id().c_str())); 129 } 130 131 return rocksdb::Status::OK(); 132 } 133 134 // GenerateDataKey takes a pointer to a registry (should be a temporary registry, not the 135 // key manager's private registry) and generates a data key based on the active store key. 136 rocksdb::Status GenerateDataKey(rocksdb::Env* env, enginepbccl::DataKeysRegistry* reg) { 137 // Get the active store key. 138 auto iter = reg->store_keys().find(reg->active_store_key_id()); 139 // The caller should have either called ValidateRegistry or set a store key. 140 assert(iter != reg->store_keys().cend()); 141 142 // Generate a new key. 143 enginepbccl::SecretKey new_key; 144 auto status = KeyManagerUtils::KeyFromKeyInfo(env, iter->second, &new_key); 145 if (!status.ok()) { 146 return status; 147 } 148 149 // Store new data key and mark as active. 150 (*reg->mutable_data_keys())[new_key.info().key_id()] = new_key; 151 reg->set_active_data_key_id(new_key.info().key_id()); 152 153 return rocksdb::Status::OK(); 154 } 155 156 std::string AlgorithmEnumToString(const enginepbccl::EncryptionType t) { 157 switch (t) { 158 case enginepbccl::AES128_CTR: 159 return "AES128-CTR"; 160 break; 161 case enginepbccl::AES192_CTR: 162 return "AES192-CTR"; 163 break; 164 case enginepbccl::AES256_CTR: 165 return "AES256-CTR"; 166 break; 167 default: 168 return fmt::StringPrintf("unknown EncryptionType: %d", t); 169 } 170 } 171 172 std::string StoreKeyInfoSummary(const enginepbccl::KeyInfo& info) { 173 if (info.encryption_type() == enginepbccl::Plaintext) { 174 return "plain"; 175 } 176 return fmt::StringPrintf("ID: %s, Type: %s, Source: %s", info.key_id().c_str(), 177 AlgorithmEnumToString(info.encryption_type()).c_str(), 178 info.source().c_str()); 179 } 180 181 std::string DataKeyInfoSummary(const enginepbccl::KeyInfo& info) { 182 if (info.encryption_type() == enginepbccl::Plaintext) { 183 return "plain"; 184 } 185 return fmt::StringPrintf("ID: %s, Type: %s, Parent Key ID: %s", info.key_id().c_str(), 186 AlgorithmEnumToString(info.encryption_type()).c_str(), 187 info.parent_key_id().c_str()); 188 } 189 190 }; // namespace KeyManagerUtils 191 192 KeyManager::~KeyManager() {} 193 194 FileKeyManager::FileKeyManager(rocksdb::Env* env, std::shared_ptr<rocksdb::Logger> logger, 195 const std::string& active_key_path, const std::string& old_key_path) 196 : env_(env), logger_(logger), active_key_path_(active_key_path), old_key_path_(old_key_path) {} 197 198 FileKeyManager::~FileKeyManager() {} 199 200 rocksdb::Status FileKeyManager::LoadKeys() { 201 std::shared_ptr<enginepbccl::SecretKey> active(new enginepbccl::SecretKey()); 202 rocksdb::Status status = KeyManagerUtils::KeyFromFile(env_, active_key_path_, active.get()); 203 if (!status.ok()) { 204 return status; 205 } 206 207 std::shared_ptr<enginepbccl::SecretKey> old(new enginepbccl::SecretKey()); 208 status = KeyManagerUtils::KeyFromFile(env_, old_key_path_, old.get()); 209 if (!status.ok()) { 210 return status; 211 } 212 213 rocksdb::Info(logger_, "loaded active store key: %s, old store key: %s", 214 KeyManagerUtils::StoreKeyInfoSummary(active->info()).c_str(), 215 KeyManagerUtils::StoreKeyInfoSummary(old->info()).c_str()); 216 217 active_key_.swap(active); 218 old_key_.swap(old); 219 return rocksdb::Status::OK(); 220 } 221 222 std::shared_ptr<enginepbccl::SecretKey> FileKeyManager::CurrentKey() { return active_key_; } 223 224 std::shared_ptr<enginepbccl::SecretKey> FileKeyManager::GetKey(const std::string& id) { 225 if (active_key_ != nullptr && active_key_->info().key_id() == id) { 226 return active_key_; 227 } 228 if (old_key_ != nullptr && old_key_->info().key_id() == id) { 229 return old_key_; 230 } 231 return nullptr; 232 } 233 234 DataKeyManager::~DataKeyManager() {} 235 236 DataKeyManager::DataKeyManager(rocksdb::Env* env, std::shared_ptr<rocksdb::Logger> logger, 237 const std::string& db_dir, int64_t rotation_period, bool read_only) 238 : env_(env), 239 logger_(logger), 240 registry_path_(db_dir + "/" + kKeyRegistryFilename), 241 rotation_period_(rotation_period), 242 read_only_(read_only) { 243 auto status = env_->NewDirectory(db_dir, ®istry_dir_); 244 if (!status.ok()) { 245 rocksdb::Fatal(logger_, "unable to open directory %s to sync: %s", db_dir.c_str(), status.ToString().c_str()); 246 } 247 } 248 249 rocksdb::Status DataKeyManager::LoadKeysHelper(enginepbccl::DataKeysRegistry* registry) { 250 rocksdb::Status status = env_->FileExists(registry_path_); 251 if (status.IsNotFound()) { 252 // First run: we'll write the file soon enough. 253 return rocksdb::Status::OK(); 254 } else if (!status.ok()) { 255 return status; 256 } 257 258 std::string contents; 259 status = rocksdb::ReadFileToString(env_, registry_path_, &contents); 260 if (!status.ok()) { 261 return status; 262 } 263 264 if (!registry->ParseFromString(contents)) { 265 return rocksdb::Status::InvalidArgument( 266 fmt::StringPrintf("failed to parse key registry %s", registry_path_.c_str())); 267 } 268 269 return rocksdb::Status::OK(); 270 } 271 272 rocksdb::Status DataKeyManager::LoadKeys() { 273 std::unique_lock<std::mutex> l(mu_); 274 275 // We should never have loaded keys before. 276 assert(current_key_ == nullptr); 277 assert(registry_ == nullptr); 278 279 std::unique_ptr<enginepbccl::DataKeysRegistry> registry(new enginepbccl::DataKeysRegistry()); 280 auto status = LoadKeysHelper(registry.get()); 281 if (!status.ok()) { 282 return status; 283 } 284 285 status = KeyManagerUtils::ValidateRegistry(registry.get()); 286 if (!status.ok()) { 287 return status; 288 } 289 290 registry_.swap(registry); 291 current_key_ = CurrentKeyLocked(); 292 293 if (current_key_ == nullptr) { 294 rocksdb::Info(logger_, "no active data key yet"); 295 } else { 296 rocksdb::Info(logger_, "loaded active data key: %s", 297 KeyManagerUtils::DataKeyInfoSummary(current_key_->info()).c_str()); 298 } 299 300 return rocksdb::Status::OK(); 301 } 302 303 std::shared_ptr<enginepbccl::SecretKey> DataKeyManager::CurrentKey() { 304 std::unique_lock<std::mutex> l(mu_); 305 auto status = MaybeRotateKeyLocked(); 306 if (!status.ok()) { 307 rocksdb::Error(logger_, "error while attempting to rotate data key: %s", status.getState()); 308 } 309 return current_key_; 310 } 311 312 std::shared_ptr<enginepbccl::SecretKey> DataKeyManager::CurrentKeyLocked() { 313 assert(registry_ != nullptr); 314 if (registry_->active_data_key_id() == "") { 315 return nullptr; 316 } 317 318 auto iter = registry_->data_keys().find(registry_->active_data_key_id()); 319 320 // Any modification of the registry should have called Validate. 321 assert(iter != registry_->data_keys().cend()); 322 return std::shared_ptr<enginepbccl::SecretKey>(new enginepbccl::SecretKey(iter->second)); 323 } 324 325 std::shared_ptr<enginepbccl::SecretKey> DataKeyManager::GetKey(const std::string& id) { 326 std::unique_lock<std::mutex> l(mu_); 327 328 assert(registry_ != nullptr); 329 330 if (id == registry_->active_data_key_id()) { 331 // Shortcut: this is the current key. 332 return current_key_; 333 } 334 335 auto key = registry_->data_keys().find(id); 336 if (key == registry_->data_keys().cend()) { 337 return nullptr; 338 } 339 return std::shared_ptr<enginepbccl::SecretKey>(new enginepbccl::SecretKey(key->second)); 340 } 341 342 std::unique_ptr<enginepbccl::KeyInfo> DataKeyManager::GetActiveStoreKeyInfo() { 343 std::unique_lock<std::mutex> l(mu_); 344 345 assert(registry_ != nullptr); 346 if (registry_->active_store_key_id() == "") { 347 return nullptr; 348 } 349 350 auto iter = registry_->store_keys().find(registry_->active_store_key_id()); 351 352 // Any modification of the registry should have called Validate. 353 assert(iter != registry_->store_keys().cend()); 354 return std::unique_ptr<enginepbccl::KeyInfo>(new enginepbccl::KeyInfo(iter->second)); 355 } 356 357 std::unique_ptr<enginepbccl::DataKeysRegistry> DataKeyManager::GetScrubbedRegistry() const { 358 std::unique_lock<std::mutex> l(mu_); 359 if (registry_ == nullptr) { 360 return nullptr; 361 } 362 363 auto new_registry = 364 std::unique_ptr<enginepbccl::DataKeysRegistry>(new enginepbccl::DataKeysRegistry(*registry_)); 365 auto keys = new_registry->mutable_data_keys(); 366 for (auto key_iter = keys->begin(); key_iter != keys->end(); ++key_iter) { 367 key_iter->second.clear_key(); 368 } 369 370 return new_registry; 371 } 372 373 rocksdb::Status DataKeyManager::MaybeRotateKeyLocked() { 374 assert(registry_ != nullptr); 375 376 if (registry_->active_store_key_id() == "" || registry_->active_data_key_id() == "") { 377 return rocksdb::Status::InvalidArgument( 378 "MaybeRotateKey called before SetActiveStoreKeyInfo: there is no key to rotate"); 379 } 380 381 assert(current_key_ != nullptr); 382 383 if (current_key_->info().encryption_type() == enginepbccl::Plaintext) { 384 // There's no point in rotating plaintext. 385 return rocksdb::Status::OK(); 386 } 387 388 int64_t now; 389 auto status = env_->GetCurrentTime(&now); 390 if (!status.ok()) { 391 return status; 392 } 393 394 if ((now - current_key_->info().creation_time()) < rotation_period_) { 395 return rocksdb::Status::OK(); 396 } 397 398 // We need a new key. Copy the registry first. 399 auto new_registry = 400 std::unique_ptr<enginepbccl::DataKeysRegistry>(new enginepbccl::DataKeysRegistry(*registry_)); 401 402 // Generate and store a new data key. 403 status = KeyManagerUtils::GenerateDataKey(env_, new_registry.get()); 404 if (!status.ok()) { 405 return status; 406 } 407 408 status = PersistRegistryLocked(std::move(new_registry)); 409 if (!status.ok()) { 410 return status; 411 } 412 413 assert(current_key_ != nullptr); 414 rocksdb::Info(logger_, "rotated to new active data key: %s", 415 KeyManagerUtils::DataKeyInfoSummary(current_key_->info()).c_str()); 416 417 return rocksdb::Status::OK(); 418 } 419 420 rocksdb::Status 421 DataKeyManager::SetActiveStoreKeyInfo(std::unique_ptr<enginepbccl::KeyInfo> store_info) { 422 std::unique_lock<std::mutex> l(mu_); 423 424 assert(registry_ != nullptr); 425 if (registry_->active_store_key_id() == store_info->key_id()) { 426 // We currently have this store key marked active: check if we need a refresh. 427 return MaybeRotateKeyLocked(); 428 } 429 430 if (store_info->encryption_type() != enginepbccl::Plaintext) { 431 // Make sure the key doesn't exist yet for keys other than plaintext. 432 // If we are not currently using plaintext, we're ok overwriting an older "plain" key. 433 // TODO(mberhault): Are there cases we may want to allow? 434 if (registry_->store_keys().find(store_info->key_id()) != registry_->store_keys().cend()) { 435 return rocksdb::Status::InvalidArgument( 436 fmt::StringPrintf("new active store key ID %s already exists as an inactive key. This " 437 "is really dangerous.", 438 store_info->key_id().c_str())); 439 } 440 } 441 442 // Make a copy of the registry, add store key info to the list of store keys, and mark as 443 // active. 444 auto new_registry = 445 std::unique_ptr<enginepbccl::DataKeysRegistry>(new enginepbccl::DataKeysRegistry(*registry_)); 446 (*new_registry->mutable_store_keys())[store_info->key_id()] = *store_info; 447 new_registry->set_active_store_key_id(store_info->key_id()); 448 449 if (store_info->encryption_type() == enginepbccl::Plaintext) { 450 // This is a plaintext store key: mark all data keys as exposed. 451 for (auto it = new_registry->mutable_data_keys()->begin(); 452 it != new_registry->mutable_data_keys()->end(); ++it) { 453 it->second.mutable_info()->set_was_exposed(true); 454 } 455 } 456 457 // Generate and store a new data key. 458 auto status = KeyManagerUtils::GenerateDataKey(env_, new_registry.get()); 459 if (!status.ok()) { 460 return status; 461 } 462 463 status = PersistRegistryLocked(std::move(new_registry)); 464 if (!status.ok()) { 465 return status; 466 } 467 468 assert(current_key_ != nullptr); 469 rocksdb::Info(logger_, "generated new active data key: %s", 470 KeyManagerUtils::DataKeyInfoSummary(current_key_->info()).c_str()); 471 472 return rocksdb::Status::OK(); 473 } 474 475 rocksdb::Status 476 DataKeyManager::PersistRegistryLocked(std::unique_ptr<enginepbccl::DataKeysRegistry> reg) { 477 if (read_only_) { 478 return rocksdb::Status::InvalidArgument("key manager is read-only, keys cannot be rotated"); 479 } 480 481 // Validate before writing. 482 auto status = KeyManagerUtils::ValidateRegistry(reg.get()); 483 if (!status.ok()) { 484 return status; 485 } 486 487 // Serialize and write to file. 488 std::string contents; 489 if (!reg->SerializeToString(&contents)) { 490 return rocksdb::Status::InvalidArgument("failed to serialize key registry"); 491 } 492 493 status = SafeWriteStringToFile(env_, registry_dir_.get(), registry_path_, contents); 494 if (!status.ok()) { 495 return status; 496 } 497 498 // Swap registry. 499 registry_.swap(reg); 500 current_key_ = CurrentKeyLocked(); 501 502 return rocksdb::Status::OK(); 503 }