// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////

#ifndef TINK_KEYSET_HANDLE_H_
#define TINK_KEYSET_HANDLE_H_

#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "absl/base/attributes.h"
#include "absl/container/flat_hash_map.h"
#include "absl/memory/memory.h"
#include "absl/status/status.h"
#include "tink/aead.h"
#include "tink/configuration.h"
#include "tink/internal/configuration_impl.h"
#include "tink/internal/key_info.h"
#include "tink/key.h"
#include "tink/key_gen_configuration.h"
#include "tink/key_manager.h"
#include "tink/key_status.h"
#include "tink/keyset_reader.h"
#include "tink/keyset_writer.h"
#include "tink/primitive_set.h"
#include "tink/registry.h"
#include "tink/util/statusor.h"
#include "proto/tink.pb.h"

namespace crypto {
namespace tink {

// KeysetHandle provides abstracted access to Keysets, to limit
// the exposure of actual protocol buffers that hold sensitive
// key material.
class KeysetHandle {
 public:
  // Represents a single entry in a `KeysetHandle`. Some current behavior will
  // be changed in the future.
  class Entry {
   public:
    // May return an internal class in case there is no implementation of the
    // corresponding key class yet.  Returned value only valid for lifetime
    // of entry object.
    std::shared_ptr<const Key> GetKey() const { return key_; }

    // Status indicates whether or not a key should still be used.
    KeyStatus GetStatus() const { return status_; }

    // ID should be unique (though currently Tink still accepts keysets with
    // repeated IDs).
    int GetId() const { return id_; }

    // Should return true for exactly one entry (though currently Tink still
    // accepts keysets which have no entry marked as primary).
    bool IsPrimary() const { return is_primary_; }

   private:
    friend class KeysetHandle;
    friend class KeysetHandleBuilder;

    Entry(std::shared_ptr<const Key> key, KeyStatus status, int id,
          bool is_primary)
        : key_(std::move(key)),
          status_(status),
          id_(id),
          is_primary_(is_primary) {}

    std::shared_ptr<const Key> key_;
    KeyStatus status_;
    int id_;
    bool is_primary_;
  };

  // Returns the number of entries in this keyset.
  int size() const { return keyset_.key_size(); }
  // Validates single `KeysetHandle::Entry` at `index` by making sure that the
  // key entry's type URL is printable and that it has a valid key status.
  crypto::tink::util::Status ValidateAt(int index) const;
  // Validates each individual `KeysetHandle::Entry` in keyset handle by calling
  // `ValidateAt()`.  Also, checks that there is a single enabled primary key.
  crypto::tink::util::Status Validate() const;
  // Returns entry for primary key in this keyset. Crashes if `Validate()`
  // does not return an OK status.  Call `Validate()` prior to calling this
  // method to avoid potentially crashing your program.
  Entry GetPrimary() const;
  // Returns the `KeysetHandle::Entry` at `index`.  Crashes if
  // `ValidateAt(index)` does not return an OK status.  Call `ValidateAt(index)`
  // prior to calling this method to avoid potentially crashing your program.
  Entry operator[](int index) const;

  // Creates a KeysetHandle from an encrypted keyset obtained via `reader`
  // using `master_key_aead` to decrypt the keyset, with monitoring annotations
  // `monitoring_annotations`; by default, `monitoring_annotations` is empty.
  static crypto::tink::util::StatusOr<std::unique_ptr<KeysetHandle>> Read(
      std::unique_ptr<KeysetReader> reader, const Aead& master_key_aead,
      const absl::flat_hash_map<std::string, std::string>&
          monitoring_annotations = {});

  // Creates a KeysetHandle from an encrypted keyset obtained via `reader`
  // using `master_key_aead` to decrypt the keyset, expecting `associated_data`.
  // The keyset is annotated for monitoring with `monitoring_annotations`; by
  // default, `monitoring_annotations` is empty.
  static crypto::tink::util::StatusOr<std::unique_ptr<KeysetHandle>>
  ReadWithAssociatedData(std::unique_ptr<KeysetReader> reader,
                         const Aead& master_key_aead,
                         absl::string_view associated_data,
                         const absl::flat_hash_map<std::string, std::string>&
                             monitoring_annotations = {});

  // Creates a KeysetHandle from a serialized keyset `serialized_keyset` which
  // contains no secret key material, and annotates it with
  // `monitoring_annotations` for monitoring; by default,
  // `monitoring_annotations` is empty. This can be used to load public keysets
  // or envelope encryption keysets.
  static crypto::tink::util::StatusOr<std::unique_ptr<KeysetHandle>>
  ReadNoSecret(const std::string& serialized_keyset,
               const absl::flat_hash_map<std::string, std::string>&
                   monitoring_annotations = {});

  // Returns a KeysetHandle containing a single new key generated according to
  // `key_template` and using `config`. The keyset is annotated for monitoring
  // with `monitoring_annotations`, which is empty by default.
  static crypto::tink::util::StatusOr<std::unique_ptr<KeysetHandle>>
  GenerateNew(const google::crypto::tink::KeyTemplate& key_template,
              const crypto::tink::KeyGenConfiguration& config,
              const absl::flat_hash_map<std::string, std::string>&
                  monitoring_annotations = {});

  // TODO(b/265865177): Deprecate.
  // Returns a KeysetHandle containing a single new key generated according to
  // `key_template`. The keyset is annotated for monitoring with
  // `monitoring_annotations`, which is empty by default.
  static crypto::tink::util::StatusOr<std::unique_ptr<KeysetHandle>>
  GenerateNew(const google::crypto::tink::KeyTemplate& key_template,
              const absl::flat_hash_map<std::string, std::string>&
                  monitoring_annotations = {});

  // Encrypts the underlying keyset with the provided `master_key_aead`
  // and writes the resulting EncryptedKeyset to the given `writer`,
  // which must be non-null.
  crypto::tink::util::Status Write(KeysetWriter* writer,
                                   const Aead& master_key_aead) const;

  // Encrypts the underlying keyset with the provided `master_key_aead`, using
  // `associated_data`. and writes the resulting EncryptedKeyset to the given
  // `writer`, which must be non-null.
  crypto::tink::util::Status WriteWithAssociatedData(
      KeysetWriter* writer, const Aead& master_key_aead,
      absl::string_view associated_data) const;

  // Returns KeysetInfo, a "safe" Keyset that doesn't contain any actual
  // key material, thus can be used for logging or monitoring.
  google::crypto::tink::KeysetInfo GetKeysetInfo() const;

  // Writes the underlying keyset to `writer` only if the keyset does not
  // contain any secret key material.
  // This can be used to persist public keysets or envelope encryption keysets.
  // Users that need to persist cleartext keysets can use
  // `CleartextKeysetHandle`.
  crypto::tink::util::Status WriteNoSecret(KeysetWriter* writer) const;

  // Returns a new KeysetHandle containing public keys corresponding to the
  // private keys in this handle. Relies on key type managers stored in `config`
  // to do so. Returns an error if this handle contains keys that are not
  // private keys.
  crypto::tink::util::StatusOr<std::unique_ptr<KeysetHandle>>
  GetPublicKeysetHandle(const KeyGenConfiguration& config) const;

  // Returns a new KeysetHandle containing public keys corresponding to the
  // private keys in this handle. Relies on key type managers stored in the
  // global registry to do so. Returns an error if this handle contains keys
  // that are not private keys.
  crypto::tink::util::StatusOr<std::unique_ptr<KeysetHandle>>
  GetPublicKeysetHandle() const;

  // Creates a wrapped primitive using this keyset handle and config, which
  // stores necessary primitive wrappers and key type managers.
  template <class P>
  crypto::tink::util::StatusOr<std::unique_ptr<P>> GetPrimitive(
      const Configuration& config) const;

  // Creates a wrapped primitive using this keyset handle and the global
  // registry, which stores necessary primitive wrappers and key type managers.
  template <class P>
  crypto::tink::util::StatusOr<std::unique_ptr<P>> GetPrimitive() const;

  // Creates a wrapped primitive corresponding to this keyset. Uses the given
  // KeyManager, as well as the KeyManager and PrimitiveWrapper objects in the
  // global registry to create the primitive. The given KeyManager is used for
  // keys supported by it. For those, the registry is ignored.
  template <class P>
  ABSL_DEPRECATED("Register the keymanager and use GetPrimitive")
  crypto::tink::util::StatusOr<std::unique_ptr<P>> GetPrimitive(
      const KeyManager<P>* custom_manager) const;

 private:
  // The classes below need access to get_keyset();
  friend class CleartextKeysetHandle;
  friend class KeysetManager;

  // TestKeysetHandle::GetKeyset() provides access to get_keyset().
  friend class TestKeysetHandle;

  // KeysetHandleBuilder::Build() needs access to KeysetHandle(Keyset).
  friend class KeysetHandleBuilder;

  // Creates a handle that contains the given keyset.
  explicit KeysetHandle(google::crypto::tink::Keyset keyset)
      : keyset_(std::move(keyset)) {}
  explicit KeysetHandle(std::unique_ptr<google::crypto::tink::Keyset> keyset)
      : keyset_(std::move(*keyset)) {}
  // Creates a handle that contains the given `keyset` and `entries`.
  explicit KeysetHandle(
      google::crypto::tink::Keyset keyset,
      const std::vector<std::shared_ptr<const Entry>>& entries)
      : keyset_(std::move(keyset)), entries_(entries) {}
  explicit KeysetHandle(
      std::unique_ptr<google::crypto::tink::Keyset> keyset,
      const std::vector<std::shared_ptr<const Entry>>& entries)
      : keyset_(std::move(*keyset)), entries_(entries) {}
  // Creates a handle that contains the given `keyset` and
  // `monitoring_annotations`.
  KeysetHandle(google::crypto::tink::Keyset keyset,
               const absl::flat_hash_map<std::string, std::string>&
                   monitoring_annotations)
      : keyset_(std::move(keyset)),
        monitoring_annotations_(monitoring_annotations) {}
  KeysetHandle(std::unique_ptr<google::crypto::tink::Keyset> keyset,
               const absl::flat_hash_map<std::string, std::string>&
                   monitoring_annotations)
      : keyset_(std::move(*keyset)),
        monitoring_annotations_(monitoring_annotations) {}
  // Creates a handle that contains the given `keyset`, `entries`, and
  // `monitoring_annotations`.
  KeysetHandle(google::crypto::tink::Keyset keyset,
               const std::vector<std::shared_ptr<const Entry>>& entries,
               const absl::flat_hash_map<std::string, std::string>&
                   monitoring_annotations)
      : keyset_(std::move(keyset)),
        entries_(entries),
        monitoring_annotations_(monitoring_annotations) {}
  KeysetHandle(std::unique_ptr<google::crypto::tink::Keyset> keyset,
               const std::vector<std::shared_ptr<const Entry>>& entries,
               const absl::flat_hash_map<std::string, std::string>&
                   monitoring_annotations)
      : keyset_(std::move(*keyset)),
        entries_(entries),
        monitoring_annotations_(monitoring_annotations) {}

  // Generates a key from `key_template` and adds it `keyset`.
  static crypto::tink::util::StatusOr<uint32_t> AddToKeyset(
      const google::crypto::tink::KeyTemplate& key_template, bool as_primary,
      const crypto::tink::KeyGenConfiguration& config,
      google::crypto::tink::Keyset* keyset);

  // Creates list of KeysetHandle::Entry entries derived from `keyset` in order.
  static crypto::tink::util::StatusOr<std::vector<std::shared_ptr<const Entry>>>
  GetEntriesFromKeyset(const google::crypto::tink::Keyset& keyset);

  // Creates KeysetHandle::Entry for `key`, which will be set to primary if
  // its key id equals `primary_key_id`.
  static util::StatusOr<Entry> CreateEntry(
      const google::crypto::tink::Keyset::Key& key, uint32_t primary_key_id);

  // Generates a key from `key_template` and adds it to the keyset handle.
  crypto::tink::util::StatusOr<uint32_t> AddKey(
      const google::crypto::tink::KeyTemplate& key_template, bool as_primary,
      const crypto::tink::KeyGenConfiguration& config);

  // Returns keyset held by this handle.
  const google::crypto::tink::Keyset& get_keyset() const { return keyset_; }

  // Creates a set of primitives corresponding to the keys with
  // (status == ENABLED) in the keyset given in 'keyset_handle',
  // assuming all the corresponding key managers are present (keys
  // with (status != ENABLED) are skipped).
  //
  // The returned set is usually later "wrapped" into a class that
  // implements the corresponding Primitive-interface.
  template <class P>
  crypto::tink::util::StatusOr<std::unique_ptr<PrimitiveSet<P>>> GetPrimitives(
      const KeyManager<P>* custom_manager) const;

  // Creates KeysetHandle::Entry from `keyset_` at `index`.
  Entry CreateEntryAt(int index) const;

  google::crypto::tink::Keyset keyset_;
  // If this keyset handle has been created with a constructor that does not
  // accept an entries argument, then `entries` will be empty and operator[]
  // will fall back to creating the key entry on demand from `keyset_`.
  //
  // If `entries_` is not empty, then it should contain exactly one key entry
  // for each key proto in `keyset_`.
  std::vector<std::shared_ptr<const Entry>> entries_;
  absl::flat_hash_map<std::string, std::string> monitoring_annotations_;
};

///////////////////////////////////////////////////////////////////////////////
// Implementation details of templated methods.

template <class P>
crypto::tink::util::StatusOr<std::unique_ptr<PrimitiveSet<P>>>
KeysetHandle::GetPrimitives(const KeyManager<P>* custom_manager) const {
  crypto::tink::util::Status status = ValidateKeyset(get_keyset());
  if (!status.ok()) return status;
  typename PrimitiveSet<P>::Builder primitives_builder;
  primitives_builder.AddAnnotations(monitoring_annotations_);
  for (const google::crypto::tink::Keyset::Key& key : get_keyset().key()) {
    if (key.status() == google::crypto::tink::KeyStatusType::ENABLED) {
      std::unique_ptr<P> primitive;
      if (custom_manager != nullptr &&
          custom_manager->DoesSupport(key.key_data().type_url())) {
        auto primitive_result = custom_manager->GetPrimitive(key.key_data());
        if (!primitive_result.ok()) return primitive_result.status();
        primitive = std::move(primitive_result.value());
      } else {
        auto primitive_result = Registry::GetPrimitive<P>(key.key_data());
        if (!primitive_result.ok()) return primitive_result.status();
        primitive = std::move(primitive_result.value());
      }
      if (key.key_id() == get_keyset().primary_key_id()) {
        primitives_builder.AddPrimaryPrimitive(std::move(primitive),
                                               KeyInfoFromKey(key));
      } else {
        primitives_builder.AddPrimitive(std::move(primitive),
                                        KeyInfoFromKey(key));
      }
    }
  }
  auto primitives = std::move(primitives_builder).Build();
  if (!primitives.ok()) return primitives.status();
  return absl::make_unique<PrimitiveSet<P>>(*std::move(primitives));
}

template <class P>
crypto::tink::util::StatusOr<std::unique_ptr<P>> KeysetHandle::GetPrimitive(
    const Configuration& config) const {
  if (crypto::tink::internal::ConfigurationImpl::IsInGlobalRegistryMode(
          config)) {
    return crypto::tink::internal::RegistryImpl::GlobalInstance().WrapKeyset<P>(
        keyset_, monitoring_annotations_);
  }

  crypto::tink::util::StatusOr<
      const crypto::tink::internal::KeysetWrapperStore*>
      wrapper_store =
          crypto::tink::internal::ConfigurationImpl::GetKeysetWrapperStore(
              config);
  if (!wrapper_store.ok()) {
    return wrapper_store.status();
  }
  crypto::tink::util::StatusOr<const crypto::tink::internal::KeysetWrapper<P>*>
      wrapper = (*wrapper_store)->Get<P>();
  if (!wrapper.ok()) {
    return wrapper.status();
  }
  return (*wrapper)->Wrap(keyset_, monitoring_annotations_);
}

// TODO(b/265865177): Deprecate.
template <class P>
crypto::tink::util::StatusOr<std::unique_ptr<P>> KeysetHandle::GetPrimitive()
    const {
  // TODO(b/265705174): Replace with ConfigGlobalRegistry instance.
  crypto::tink::Configuration config;
  crypto::tink::util::Status status =
      crypto::tink::internal::ConfigurationImpl::SetGlobalRegistryMode(config);
  if (!status.ok()) {
    return status;
  }
  return GetPrimitive<P>(config);
}

template <class P>
crypto::tink::util::StatusOr<std::unique_ptr<P>> KeysetHandle::GetPrimitive(
    const KeyManager<P>* custom_manager) const {
  if (custom_manager == nullptr) {
    return crypto::tink::util::Status(absl::StatusCode::kInvalidArgument,
                                      "custom_manager must not be null");
  }
  auto primitives_result = this->GetPrimitives<P>(custom_manager);
  if (!primitives_result.ok()) {
    return primitives_result.status();
  }
  return Registry::Wrap<P>(std::move(primitives_result.value()));
}

}  // namespace tink
}  // namespace crypto

#endif  // TINK_KEYSET_HANDLE_H_
