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, &registry_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  }