// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "quiche/quic/core/crypto/crypto_secret_boxer.h"

#include <cstdint>
#include <string>

#include "absl/strings/string_view.h"
#include "openssl/aead.h"
#include "openssl/err.h"
#include "quiche/quic/core/crypto/quic_random.h"
#include "quiche/quic/platform/api/quic_logging.h"

namespace quic {

// kSIVNonceSize contains the number of bytes of nonce in each AES-GCM-SIV box.
// AES-GCM-SIV takes a 12-byte nonce and, since the messages are so small, each
// key is good for more than 2^64 source-address tokens. See table 1 of
// https://eprint.iacr.org/2017/168.pdf
static const size_t kSIVNonceSize = 12;

// AES-GCM-SIV comes in AES-128 and AES-256 flavours. The AES-256 version is
// used here so that the key size matches the 256-bit XSalsa20 keys that we
// used to use.
static const size_t kBoxKeySize = 32;

struct CryptoSecretBoxer::State {
  // ctxs are the initialised AEAD contexts. These objects contain the
  // scheduled AES state for each of the keys.
  std::vector<bssl::UniquePtr<EVP_AEAD_CTX>> ctxs;
};

CryptoSecretBoxer::CryptoSecretBoxer() {}

CryptoSecretBoxer::~CryptoSecretBoxer() {}

// static
size_t CryptoSecretBoxer::GetKeySize() { return kBoxKeySize; }

// kAEAD is the AEAD used for boxing: AES-256-GCM-SIV.
static const EVP_AEAD* (*const kAEAD)() = EVP_aead_aes_256_gcm_siv;

bool CryptoSecretBoxer::SetKeys(const std::vector<std::string>& keys) {
  if (keys.empty()) {
    QUIC_LOG(DFATAL) << "No keys supplied!";
    return false;
  }
  const EVP_AEAD* const aead = kAEAD();
  std::unique_ptr<State> new_state(new State);

  for (const std::string& key : keys) {
    QUICHE_DCHECK_EQ(kBoxKeySize, key.size());
    bssl::UniquePtr<EVP_AEAD_CTX> ctx(
        EVP_AEAD_CTX_new(aead, reinterpret_cast<const uint8_t*>(key.data()),
                         key.size(), EVP_AEAD_DEFAULT_TAG_LENGTH));
    if (!ctx) {
      ERR_clear_error();
      QUIC_LOG(DFATAL) << "EVP_AEAD_CTX_init failed";
      return false;
    }

    new_state->ctxs.push_back(std::move(ctx));
  }

  QuicWriterMutexLock l(&lock_);
  state_ = std::move(new_state);
  return true;
}

std::string CryptoSecretBoxer::Box(QuicRandom* rand,
                                   absl::string_view plaintext) const {
  // The box is formatted as:
  //   12 bytes of random nonce
  //   n bytes of ciphertext
  //   16 bytes of authenticator
  size_t out_len =
      kSIVNonceSize + plaintext.size() + EVP_AEAD_max_overhead(kAEAD());

  std::string ret;
  ret.resize(out_len);
  uint8_t* out = reinterpret_cast<uint8_t*>(const_cast<char*>(ret.data()));

  // Write kSIVNonceSize bytes of random nonce to the beginning of the output
  // buffer.
  rand->RandBytes(out, kSIVNonceSize);
  const uint8_t* const nonce = out;
  out += kSIVNonceSize;
  out_len -= kSIVNonceSize;

  size_t bytes_written;
  {
    QuicReaderMutexLock l(&lock_);
    if (!EVP_AEAD_CTX_seal(state_->ctxs[0].get(), out, &bytes_written, out_len,
                           nonce, kSIVNonceSize,
                           reinterpret_cast<const uint8_t*>(plaintext.data()),
                           plaintext.size(), nullptr, 0)) {
      ERR_clear_error();
      QUIC_LOG(DFATAL) << "EVP_AEAD_CTX_seal failed";
      return "";
    }
  }

  QUICHE_DCHECK_EQ(out_len, bytes_written);
  return ret;
}

bool CryptoSecretBoxer::Unbox(absl::string_view in_ciphertext,
                              std::string* out_storage,
                              absl::string_view* out) const {
  if (in_ciphertext.size() < kSIVNonceSize) {
    return false;
  }

  const uint8_t* const nonce =
      reinterpret_cast<const uint8_t*>(in_ciphertext.data());
  const uint8_t* const ciphertext = nonce + kSIVNonceSize;
  const size_t ciphertext_len = in_ciphertext.size() - kSIVNonceSize;

  out_storage->resize(ciphertext_len);

  bool ok = false;
  {
    QuicReaderMutexLock l(&lock_);
    for (const bssl::UniquePtr<EVP_AEAD_CTX>& ctx : state_->ctxs) {
      size_t bytes_written;
      if (EVP_AEAD_CTX_open(ctx.get(),
                            reinterpret_cast<uint8_t*>(
                                const_cast<char*>(out_storage->data())),
                            &bytes_written, ciphertext_len, nonce,
                            kSIVNonceSize, ciphertext, ciphertext_len, nullptr,
                            0)) {
        ok = true;
        *out = absl::string_view(out_storage->data(), bytes_written);
        break;
      }

      ERR_clear_error();
    }
  }

  return ok;
}

}  // namespace quic
