// Copyright 2021 Google LLC.
//
// 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.
//
///////////////////////////////////////////////////////////////////////////////
#include "tink/aead/internal/ssl_aead.h"

#include <algorithm>
#include <cstdint>
#include <iostream>
#include <iterator>
#include <limits>
#include <memory>
#include <string>
#include <unordered_set>
#include <vector>

#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/container/flat_hash_set.h"
#include "absl/status/status.h"
#include "absl/strings/escaping.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
#include "tink/aead/internal/wycheproof_aead.h"
#include "tink/internal/fips_utils.h"
#include "tink/internal/ssl_util.h"
#include "tink/subtle/subtle_util.h"
#include "tink/util/secret_data.h"
#include "tink/util/statusor.h"
#include "tink/util/test_matchers.h"

namespace crypto {
namespace tink {
namespace internal {
namespace {

using ::crypto::tink::test::IsOk;
using ::crypto::tink::test::StatusIs;
using ::testing::AllOf;
using ::testing::Eq;
using ::testing::Not;
using ::testing::TestParamInfo;
using ::testing::TestWithParam;
using ::testing::ValuesIn;

constexpr absl::string_view kMessage = "Some data to encrypt.";
constexpr absl::string_view kAssociatedData = "Some associated data.";
// 128 bits key.
constexpr absl::string_view k128Key = "000102030405060708090a0b0c0d0e0f";
// 256 bits key.
constexpr absl::string_view k256Key =
    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f";
// 12 bytes IV.
constexpr absl::string_view kAesGcmIvHex = "0123456789012345678901234";
// 24 bytes IV.
constexpr absl::string_view kXchacha20Poly1305IvHex =
    "012345678901234567890123456789012345678901234567";

enum CipherType {
  kAesGcm,
  kAesGcmSiv,
  kXchacha20Poly1305,
};

struct SslOneShotAeadTestParams {
  std::string test_name;
  CipherType cipher;
  int tag_size;
  absl::string_view iv_hex;
  absl::string_view key_hex;
};

// Returns a SslOneShotAead from `cipher_name` and `key`.
util::StatusOr<std::unique_ptr<SslOneShotAead>> CipherFromName(
    CipherType cipher, const util::SecretData& key) {
  switch (cipher) {
    case CipherType::kAesGcm: {
      return CreateAesGcmOneShotCrypter(key);
    }
    case CipherType::kAesGcmSiv: {
      return CreateAesGcmSivOneShotCrypter(key);
    }
    case CipherType::kXchacha20Poly1305: {
      return CreateXchacha20Poly1305OneShotCrypter(key);
    }
  }
}

using SslOneShotAeadTest = TestWithParam<SslOneShotAeadTestParams>;

TEST_P(SslOneShotAeadTest, CiphertextPlaintextSize) {
  SslOneShotAeadTestParams test_param = GetParam();
  util::StatusOr<std::unique_ptr<SslOneShotAead>> aead = CipherFromName(
      test_param.cipher, util::SecretDataFromStringView(
                             absl::HexStringToBytes(test_param.key_hex)));
  ASSERT_THAT(aead, IsOk());

  EXPECT_EQ((*aead)->CiphertextSize(kMessage.size()),
            kMessage.size() + test_param.tag_size);
  EXPECT_EQ((*aead)->PlaintextSize(kMessage.size() + test_param.tag_size),
            kMessage.size());
  // Minimum size.
  EXPECT_EQ((*aead)->PlaintextSize(test_param.tag_size), 0);
  // Smaller than the minumum.
  EXPECT_EQ((*aead)->PlaintextSize(0), 0);
}

// Tests that encryption of `message` with `associated_data`, and `iv` succeeds;
// writes the result in `ciphertext_buffer`.
void DoTestEncrypt(SslOneShotAead* aead, absl::string_view message,
                   absl::string_view associated_data, size_t tag_size,
                   absl::string_view iv, absl::Span<char> ciphertext_buffer) {
  ASSERT_GE(ciphertext_buffer.size(), message.size() + tag_size);
  util::StatusOr<int64_t> res = aead->Encrypt(
      message, associated_data, iv, absl::MakeSpan(ciphertext_buffer));
  ASSERT_THAT(res, IsOk());
  EXPECT_EQ(*res, message.size() + tag_size);
}

// Tests that decryption of `ciphertext_buffer` with `associated_data` and `iv`
// succeeds and equals `message`.
void DoTestDecrypt(SslOneShotAead* aead, absl::string_view message,
                   absl::string_view associated_data, absl::string_view iv,
                   absl::string_view ciphertext_buffer) {
  std::string plaintext_buff;
  subtle::ResizeStringUninitialized(&plaintext_buff, message.size());
  util::StatusOr<int64_t> written_bytes = aead->Decrypt(
      ciphertext_buffer, associated_data, iv, absl::MakeSpan(plaintext_buff));
  ASSERT_THAT(written_bytes, IsOk());
  EXPECT_EQ(*written_bytes, message.size());
  EXPECT_EQ(plaintext_buff, message);
}

TEST_P(SslOneShotAeadTest, EncryptDecrypt) {
  SslOneShotAeadTestParams test_param = GetParam();
  util::StatusOr<std::unique_ptr<SslOneShotAead>> aead = CipherFromName(
      test_param.cipher, util::SecretDataFromStringView(
                             absl::HexStringToBytes(test_param.key_hex)));
  ASSERT_THAT(aead, IsOk());

  std::string iv = absl::HexStringToBytes(test_param.iv_hex);
  std::string ciphertext_buffer;
  // Length of the message + tag.
  subtle::ResizeStringUninitialized(&ciphertext_buffer,
                                    (*aead)->CiphertextSize(kMessage.size()));
  DoTestEncrypt(aead->get(), kMessage, kAssociatedData, test_param.tag_size, iv,
                absl::MakeSpan(ciphertext_buffer));
  DoTestDecrypt(aead->get(), kMessage, kAssociatedData, iv, ciphertext_buffer);
}

// Calculates a new string with the `position`'s byte modified.
std::string ModifyString(absl::string_view input_str, int position) {
  std::string modified(input_str.data(), input_str.size());
  modified[position / 8] ^= 1 << (position % 8);
  return modified;
}

// Tests encryption/decryption with a modified ciphertext.
void DoTestEncryptDecryptWithModifiedCiphertext(SslOneShotAead* aead,
                                                size_t tag_size,
                                                absl::string_view iv) {
  std::string ciphertext_buffer;
  // Length of the message + tag.
  subtle::ResizeStringUninitialized(&ciphertext_buffer,
                                    kMessage.size() + tag_size);

  util::StatusOr<int64_t> written_bytes = aead->Encrypt(
      kMessage, kAssociatedData, iv, absl::MakeSpan(ciphertext_buffer));
  ASSERT_THAT(written_bytes, IsOk());
  EXPECT_EQ(*written_bytes, kMessage.size() + tag_size);
  std::string plaintext_buffer;
  subtle::ResizeStringUninitialized(&plaintext_buffer, kMessage.size());

  // Modify the ciphertext.
  for (size_t i = 0; i < ciphertext_buffer.size() * 8; i++) {
    EXPECT_THAT(
        aead->Decrypt(ModifyString(ciphertext_buffer, i), kAssociatedData, iv,
                      absl::MakeSpan(plaintext_buffer))
            .status(),
        Not(IsOk()))
        << i;
  }
  // Modify the associated data.
  for (size_t i = 0; i < kAssociatedData.size() * 8; i++) {
    EXPECT_THAT(
        aead->Decrypt(ciphertext_buffer, ModifyString(kAssociatedData, i), iv,
                      absl::MakeSpan(plaintext_buffer))
            .status(),
        Not(IsOk()))
        << i;
  }
  // Truncate the ciphertext.
  for (size_t i = 0; i < ciphertext_buffer.size(); i++) {
    std::string truncated_ct(ciphertext_buffer, 0, i);
    EXPECT_THAT(aead->Decrypt(truncated_ct, kAssociatedData, iv,
                              absl::MakeSpan(plaintext_buffer))
                    .status(),
                Not(IsOk()))
        << i;
  }
}

TEST_P(SslOneShotAeadTest, TestModification) {
  if (IsFipsModeEnabled()) {
    GTEST_SKIP() << "Not supported in FIPS-only mode";
  }

  SslOneShotAeadTestParams test_param = GetParam();
  util::StatusOr<std::unique_ptr<SslOneShotAead>> aead = CipherFromName(
      test_param.cipher, util::SecretDataFromStringView(
                             absl::HexStringToBytes(test_param.key_hex)));
  ASSERT_THAT(aead, IsOk());

  DoTestEncryptDecryptWithModifiedCiphertext(
      aead->get(), test_param.tag_size,
      absl::HexStringToBytes(test_param.iv_hex));
}

// Make sure that the buffer passed in to the Decrypt routine is cleared if
// decryption fails.
TEST_P(SslOneShotAeadTest, TestBufferClearsIfDecryptionFails) {
  if (IsFipsModeEnabled()) {
    GTEST_SKIP() << "Not supported in FIPS-only mode";
  }

  SslOneShotAeadTestParams test_param = GetParam();
  util::StatusOr<std::unique_ptr<SslOneShotAead>> aead = CipherFromName(
      test_param.cipher, util::SecretDataFromStringView(
                             absl::HexStringToBytes(test_param.key_hex)));
  ASSERT_THAT(aead, IsOk());

  const int64_t kCiphertextSize = kMessage.size() + test_param.tag_size;
  std::string ciphertext_buffer;
  // Length of the message + tag.
  subtle::ResizeStringUninitialized(&ciphertext_buffer, kCiphertextSize);
  std::string iv = absl::HexStringToBytes(test_param.iv_hex);
  util::StatusOr<int64_t> written_bytes = (*aead)->Encrypt(
      kMessage, kAssociatedData, iv, absl::MakeSpan(ciphertext_buffer));
  ASSERT_THAT(written_bytes, IsOk());
  EXPECT_EQ(*written_bytes, kCiphertextSize);

  std::string plaintext_buffer;
  subtle::ResizeStringUninitialized(&plaintext_buffer, kMessage.size());
  const std::string kExpectedClearedPlaintext(plaintext_buffer.size(), '\0');
  // Alter the tag.
  for (int i = kCiphertextSize - test_param.tag_size; i < kCiphertextSize;
       i++) {
    std::string modified_ciphertext = ModifyString(ciphertext_buffer, i);
    EXPECT_THAT((*aead)
                    ->Decrypt(modified_ciphertext, kAssociatedData, iv,
                              absl::MakeSpan(plaintext_buffer))
                    .status(),
                Not(IsOk()));
    EXPECT_EQ(plaintext_buffer, kExpectedClearedPlaintext);
  }
}

void TestDecryptWithEmptyAssociatedData(SslOneShotAead* aead,
                                        absl::string_view ciphertext,
                                        absl::string_view iv) {
  if (IsFipsModeEnabled()) {
    GTEST_SKIP() << "Not supported in FIPS-only mode";
  }
  std::string plaintext_buffer;
  subtle::ResizeStringUninitialized(&plaintext_buffer, kMessage.size());
  const absl::string_view empty_associated_data;
  std::vector<absl::string_view> values = {empty_associated_data,
                                           absl::string_view(), ""};
  for (auto& associated_data : values) {
    DoTestDecrypt(aead, kMessage, associated_data, iv, ciphertext);
  }
}

void DoTestWithEmptyAssociatedData(SslOneShotAead* aead, absl::string_view iv,
                                   size_t tag_size) {
  const absl::string_view empty_associated_data;
  std::vector<absl::string_view> values = {empty_associated_data,
                                           absl::string_view(), ""};
  for (auto& associated_data : values) {
    std::string ciphertext_buffer;
    subtle::ResizeStringUninitialized(&ciphertext_buffer,
                                      kMessage.size() + tag_size);
    DoTestEncrypt(aead, kMessage, associated_data, tag_size, iv,
                  absl::MakeSpan(ciphertext_buffer));
    TestDecryptWithEmptyAssociatedData(aead, ciphertext_buffer, iv);
  }
}

TEST_P(SslOneShotAeadTest, EmptyAssociatedData) {
  if (IsFipsModeEnabled()) {
    GTEST_SKIP() << "Not supported in FIPS-only mode";
  }
  SslOneShotAeadTestParams test_param = GetParam();
  util::StatusOr<std::unique_ptr<SslOneShotAead>> aead = CipherFromName(
      test_param.cipher, util::SecretDataFromStringView(
                             absl::HexStringToBytes(test_param.key_hex)));
  ASSERT_THAT(aead, IsOk());
  DoTestWithEmptyAssociatedData(aead->get(),
                                absl::HexStringToBytes(test_param.iv_hex),
                                test_param.tag_size);
}

// string_views, with `iv` and `associated_data`.
void DoTestEmptyMessageEncryptDecrypt(
    SslOneShotAead* aead, absl::string_view iv, size_t tag_size,
    absl::string_view associated_data = kAssociatedData) {
  std::string ciphertext_buffer;
  subtle::ResizeStringUninitialized(&ciphertext_buffer, tag_size);
  {  // Message is a null string_view.
    const absl::string_view message;
    DoTestEncrypt(aead, message, associated_data, tag_size, iv,
                  absl::MakeSpan(ciphertext_buffer));
    DoTestDecrypt(aead, "", associated_data, iv, ciphertext_buffer);
  }
  {  // Message is an empty string.
    const std::string message = "";
    DoTestEncrypt(aead, message, associated_data, tag_size, iv,
                  absl::MakeSpan(ciphertext_buffer));
    DoTestDecrypt(aead, "", associated_data, iv, ciphertext_buffer);
  }
  {  // Message is a default-constructed string_view.
    DoTestEncrypt(aead, absl::string_view(), associated_data, tag_size, iv,
                  absl::MakeSpan(ciphertext_buffer));
    DoTestDecrypt(aead, "", associated_data, iv, ciphertext_buffer);
  }
}

TEST_P(SslOneShotAeadTest, EmptyMessage) {
  if (IsFipsModeEnabled()) {
    GTEST_SKIP() << "Not supported in FIPS-only mode";
  }
  SslOneShotAeadTestParams test_param = GetParam();
  util::StatusOr<std::unique_ptr<SslOneShotAead>> aead = CipherFromName(
      test_param.cipher, util::SecretDataFromStringView(
                             absl::HexStringToBytes(test_param.key_hex)));
  ASSERT_THAT(aead, IsOk());
  std::string iv = absl::HexStringToBytes(test_param.iv_hex);
  DoTestEmptyMessageEncryptDecrypt(aead->get(), iv, test_param.tag_size);
}

TEST_P(SslOneShotAeadTest, EmptyMessageAndAssociatedData) {
  if (IsFipsModeEnabled()) {
    GTEST_SKIP() << "Not supported in FIPS-only mode";
  }
  SslOneShotAeadTestParams test_param = GetParam();
  util::StatusOr<std::unique_ptr<SslOneShotAead>> aead = CipherFromName(
      test_param.cipher, util::SecretDataFromStringView(
                             absl::HexStringToBytes(test_param.key_hex)));
  ASSERT_THAT(aead, IsOk());
  std::string iv = absl::HexStringToBytes(test_param.iv_hex);
  const absl::string_view default_associated_data;
  const absl::string_view empty_associated_data = "";
  DoTestEmptyMessageEncryptDecrypt(aead->get(), iv, test_param.tag_size,
                                   default_associated_data);
  DoTestEmptyMessageEncryptDecrypt(aead->get(), iv, test_param.tag_size,
                                   /*associated_data=*/absl::string_view());
  DoTestEmptyMessageEncryptDecrypt(aead->get(), iv, test_param.tag_size,
                                   empty_associated_data);
}

TEST_P(SslOneShotAeadTest, BufferOverlapEncryptFails) {
  SslOneShotAeadTestParams test_param = GetParam();
  util::StatusOr<std::unique_ptr<SslOneShotAead>> aead = CipherFromName(
      test_param.cipher, util::SecretDataFromStringView(
                             absl::HexStringToBytes(test_param.key_hex)));
  ASSERT_THAT(aead, IsOk());

  std::string ciphertext_buffer(kMessage.data(), kMessage.size());
  subtle::ResizeStringUninitialized(&ciphertext_buffer,
                                    (*aead)->CiphertextSize(kMessage.size()));

  EXPECT_THAT(
      (*aead)
          ->Encrypt(
              absl::string_view(ciphertext_buffer).substr(0, kMessage.size()),
              kAssociatedData, test_param.iv_hex,
              absl::MakeSpan(ciphertext_buffer))
          .status(),
      StatusIs(absl::StatusCode::kInvalidArgument));
}

TEST_P(SslOneShotAeadTest, BufferOverlapDecryptFails) {
  SslOneShotAeadTestParams test_param = GetParam();
  util::StatusOr<std::unique_ptr<SslOneShotAead>> aead = CipherFromName(
      test_param.cipher, util::SecretDataFromStringView(
                             absl::HexStringToBytes(test_param.key_hex)));
  ASSERT_THAT(aead, IsOk());

  std::string iv = absl::HexStringToBytes(test_param.iv_hex);
  std::string ciphertext_buffer;
  // Length of the message + tag.
  subtle::ResizeStringUninitialized(&ciphertext_buffer,
                                    (*aead)->CiphertextSize(kMessage.size()));
  DoTestEncrypt(aead->get(), kMessage, kAssociatedData, test_param.tag_size, iv,
                absl::MakeSpan(ciphertext_buffer));

  EXPECT_THAT(
      (*aead)
          ->Decrypt(
              ciphertext_buffer, kAssociatedData, iv,
              absl::MakeSpan(ciphertext_buffer).subspan(0, kMessage.size()))
          .status(),
      StatusIs(absl::StatusCode::kInvalidArgument));
}

std::vector<SslOneShotAeadTestParams> GetSslOneShotAeadTestParams() {
  std::vector<SslOneShotAeadTestParams> params = {
      {/*test_name=*/"AesGcm256", /*cipher=*/CipherType::kAesGcm,
       /*tag_size=*/kAesGcmTagSizeInBytes,
       /*iv_hex=*/kAesGcmIvHex,
       /*key_hex=*/k256Key},
      {/*test_name=*/"AesGcm128", /*cipher=*/CipherType::kAesGcm,
       /*tag_size=*/kAesGcmTagSizeInBytes,
       /*iv_hex=*/kAesGcmIvHex,
       /*key_hex=*/k128Key}};
  if (IsBoringSsl()) {
    params.push_back({/*test_name=*/"AesGcmSiv256",
                      /*cipher=*/CipherType::kAesGcmSiv,
                      /*tag_size=*/kAesGcmTagSizeInBytes,
                      /*iv_hex=*/kAesGcmIvHex,
                      /*key_hex=*/k256Key});
    params.push_back({/*test_name=*/"AesGcmSiv128",
                      /*cipher=*/CipherType::kAesGcmSiv,
                      /*tag_size=*/kAesGcmTagSizeInBytes,
                      /*iv_hex=*/kAesGcmIvHex,
                      /*key_hex=*/k128Key});
    params.push_back({/*test_name=*/"Xchacha20Poly1305",
                      /*cipher=*/CipherType::kXchacha20Poly1305,
                      /*tag_size=*/kXchacha20Poly1305TagSizeInBytes,
                      /*iv_hex=*/kXchacha20Poly1305IvHex,
                      /*key_hex=*/k256Key});
  }
  return params;
}

INSTANTIATE_TEST_SUITE_P(
    SslOneShotAeadTests, SslOneShotAeadTest,
    testing::ValuesIn(GetSslOneShotAeadTestParams()),
    [](const TestParamInfo<SslOneShotAeadTest::ParamType>& info) {
      return info.param.test_name;
    });

TEST(SslOneShotAeadTest, AesGcmTestInvalidKeySizes) {
  if (IsFipsModeEnabled()) {
    GTEST_SKIP() << "Not supported in FIPS-only mode";
  }

  for (int keysize = 0; keysize < 65; keysize++) {
    util::SecretData key(keysize, 'x');
    util::StatusOr<std::unique_ptr<SslOneShotAead>> aead =
        CreateAesGcmOneShotCrypter(key);
    if (keysize == 16 || keysize == 32) {
      EXPECT_THAT(aead, IsOk()) << "with key size " << keysize;
    } else {
      EXPECT_THAT(aead, Not(IsOk())) << "with key size " << keysize;
    }
  }
}

TEST(SslOneShotAeadTest, AesGcmSivTestInvalidKeySizes) {
  if (!IsBoringSsl()) {
    GTEST_SKIP() << "AES-GCM-SIV not supported with OpenSSL";
  }
  if (IsFipsModeEnabled()) {
    GTEST_SKIP() << "Not supported in FIPS-only mode";
  }

  for (int keysize = 0; keysize < 65; keysize++) {
    util::SecretData key(keysize, 'x');
    util::StatusOr<std::unique_ptr<SslOneShotAead>> aead =
        CreateAesGcmSivOneShotCrypter(key);
    if (keysize == 16 || keysize == 32) {
      EXPECT_THAT(aead, IsOk()) << "with key size " << keysize;
    } else {
      EXPECT_THAT(aead, Not(IsOk())) << "with key size " << keysize;
    }
  }
}

TEST(SslOneShotAeadTest, Xchacha20Poly1305TestInvalidKeySizes) {
  if (!IsBoringSsl()) {
    GTEST_SKIP() << "Xchacha20-Poly1305 not supported with OpenSSL";
  }
  if (IsFipsModeEnabled()) {
    GTEST_SKIP() << "Not supported in FIPS-only mode";
  }

  for (int keysize = 0; keysize < 65; keysize++) {
    util::SecretData key(keysize, 'x');
    util::StatusOr<std::unique_ptr<SslOneShotAead>> aead =
        CreateXchacha20Poly1305OneShotCrypter(key);
    if (keysize == 32) {
      EXPECT_THAT(aead, IsOk()) << "with key size " << keysize;
    } else {
      EXPECT_THAT(aead, Not(IsOk())) << "with key size " << keysize;
    }
  }
}

TEST(SslOneShotAeadTest, Xchacha20Poly1305TestFipsOnly) {
  if (!IsBoringSsl()) {
    GTEST_SKIP() << "Xchacha20-Poly1305 not supported with OpenSSL";
  }
  if (!IsFipsModeEnabled()) {
    GTEST_SKIP() << "Only supported in FIPS-only mode";
  }

  util::StatusOr<std::unique_ptr<SslOneShotAead>> aead =
      CreateXchacha20Poly1305OneShotCrypter(
          util::SecretDataFromStringView(absl::HexStringToBytes(k256Key)));
  EXPECT_THAT(aead.status(), StatusIs(absl::StatusCode::kInternal));
}

TEST(SslOneShotAeadTest, AesGcmTestFipsOnly) {
  if (IsFipsModeEnabled() && !IsFipsEnabledInSsl()) {
    GTEST_SKIP() << "Test should not run in FIPS mode when BoringCrypto is "
                    "unavailable.";
  }

  util::SecretData key_128 =
      util::SecretDataFromStringView(absl::HexStringToBytes(k128Key));
  util::SecretData key_256 =
      util::SecretDataFromStringView(absl::HexStringToBytes(k256Key));

  EXPECT_THAT(CreateAesGcmOneShotCrypter(key_128), IsOk());
  EXPECT_THAT(CreateAesGcmOneShotCrypter(key_256), IsOk());
}

TEST(SslOneShotAeadTest, AesGcmTestTestFipsFailWithoutBoringCrypto) {
  if (!IsFipsModeEnabled() || IsFipsEnabledInSsl()) {
    GTEST_SKIP()
        << "Test assumes kOnlyUseFips but BoringCrypto is unavailable.";
  }

  util::SecretData key_128 =
      util::SecretDataFromStringView(absl::HexStringToBytes(k128Key));
  util::SecretData key_256 =
      util::SecretDataFromStringView(absl::HexStringToBytes(k256Key));

  EXPECT_THAT(CreateAesGcmOneShotCrypter(key_128).status(),
              StatusIs(absl::StatusCode::kInternal));
  EXPECT_THAT(CreateAesGcmOneShotCrypter(key_256).status(),
              StatusIs(absl::StatusCode::kInternal));
}

TEST(AesGcmSivBoringSslTest, AesGcmTestSivTestFipsOnly) {
  if (!IsFipsModeEnabled()) {
    GTEST_SKIP() << "Only supported in FIPS-only mode";
  }

  util::SecretData key_128 =
      util::SecretDataFromStringView(absl::HexStringToBytes(k128Key));
  util::SecretData key_256 =
      util::SecretDataFromStringView(absl::HexStringToBytes(k256Key));

  EXPECT_THAT(CreateAesGcmSivOneShotCrypter(key_128).status(),
              StatusIs(absl::StatusCode::kInternal));
  EXPECT_THAT(CreateAesGcmSivOneShotCrypter(key_256).status(),
              StatusIs(absl::StatusCode::kInternal));
}

// Parameters for SslOneShotAeadWycheproofTest.
struct SslOneShotAeadWycheproofTestParams {
  std::string test_name;
  CipherType cipher;
  int nonce_size;
  int tag_size;
  absl::flat_hash_set<int> key_sizes;
  WycheproofTestVector test_vector;
};

class SslOneShotAeadWycheproofTest
    : public TestWithParam<SslOneShotAeadWycheproofTestParams> {
 public:
  void SetUp() override {
    if (IsFipsModeEnabled()) {
      GTEST_SKIP() << "Not supported in FIPS-only mode";
    }
    SslOneShotAeadWycheproofTestParams params = GetParam();
    const WycheproofTestVector& test_vector = params.test_vector;

    if (!params.key_sizes.contains(test_vector.key.size()) ||
        test_vector.nonce.size() != params.nonce_size ||
        test_vector.tag.size() != params.tag_size) {
      GTEST_SKIP() << "Unsupported parameters; key size: "
                   << test_vector.key.size()
                   << " nonce size: " << test_vector.nonce.size()
                   << " tag size: " << test_vector.tag.size();
    }
  }
};

TEST_P(SslOneShotAeadWycheproofTest, Encrypt) {
  SslOneShotAeadWycheproofTestParams params = GetParam();
  const WycheproofTestVector& test_vector = params.test_vector;
  util::SecretData key = util::SecretDataFromStringView(test_vector.key);
  util::StatusOr<std::unique_ptr<SslOneShotAead>> aead =
      CipherFromName(params.cipher, key);
  ASSERT_THAT(aead, IsOk());
  std::string ciphertext_and_tag =
      absl::StrCat(test_vector.ct, test_vector.tag);
  std::string ciphertext_buffer;
  subtle::ResizeStringUninitialized(
      &ciphertext_buffer, (*aead)->CiphertextSize(test_vector.msg.size()));
  util::StatusOr<int64_t> written_bytes =
      (*aead)->Encrypt(test_vector.msg, test_vector.aad, test_vector.nonce,
                       absl::MakeSpan(ciphertext_buffer));

  std::string expected_ciphertext =
      absl::StrCat(test_vector.ct, test_vector.tag);

  std::cout << test_vector.expected << "\n";

  if (test_vector.expected == "valid" || test_vector.expected == "acceptable") {
    ASSERT_THAT(written_bytes, IsOk());
    EXPECT_EQ(ciphertext_buffer, expected_ciphertext);
  } else {  // invalid.
    // In this case, if the resulting ciphertext/tag are different, the
    // testcase is correct.
    if (written_bytes.ok()) {
      EXPECT_THAT(ciphertext_buffer, Not(Eq(expected_ciphertext)));
    } else {
      GTEST_SUCCEED();
    }
  }
}

TEST_P(SslOneShotAeadWycheproofTest, Decrypt) {
  SslOneShotAeadWycheproofTestParams params = GetParam();
  const WycheproofTestVector& test_vector = params.test_vector;
  util::SecretData key = util::SecretDataFromStringView(test_vector.key);
  util::StatusOr<std::unique_ptr<SslOneShotAead>> aead =
      CipherFromName(params.cipher, key);
  ASSERT_THAT(aead, IsOk());
  std::string ciphertext_and_tag =
      absl::StrCat(test_vector.ct, test_vector.tag);
  std::string plaintext_buffer;
  subtle::ResizeStringUninitialized(
      &plaintext_buffer, (*aead)->PlaintextSize(ciphertext_and_tag.size()));
  util::StatusOr<int64_t> written_bytes = (*aead)->Decrypt(
      absl::StrCat(test_vector.ct, test_vector.tag), test_vector.aad,
      test_vector.nonce, absl::MakeSpan(plaintext_buffer));

  if (written_bytes.ok()) {
    EXPECT_NE(test_vector.expected, "invalid");
    EXPECT_EQ(plaintext_buffer, test_vector.msg);
  } else {
    EXPECT_THAT(test_vector.expected, Not(AllOf(Eq("valid"), Eq("acceptable"))))
        << "Could not decrypt valid/acceptable tId: " << test_vector.id
        << " iv_size: " << test_vector.nonce.size()
        << " tag_size: " << test_vector.tag.size()
        << " key_size: " << key.size() << "; error: " << written_bytes.status();
  }
}

std::vector<SslOneShotAeadWycheproofTestParams> GetWycheproofTestParams() {
  std::vector<SslOneShotAeadWycheproofTestParams> params;
  for (const WycheproofTestVector& test_vector :
       ReadWycheproofTestVectors("aes_gcm_test.json")) {
    params.push_back({/*test_name=*/"AesGcm",
                      /*cipher_name=*/CipherType::kAesGcm,
                      /*nonce_size=*/12,
                      /*tag_size=*/16,
                      /*key_sizes=*/{16, 32}, test_vector});
  }
  if (IsBoringSsl()) {
    for (const WycheproofTestVector& test_vector :
         ReadWycheproofTestVectors("aes_gcm_siv_test.json")) {
      params.push_back({/*test_name=*/"AesGcmSiv",
                        /*cipher_name=*/CipherType::kAesGcmSiv,
                        /*nonce_size=*/12,
                        /*tag_size=*/16,
                        /*key_sizes=*/{16, 32}, test_vector});
    }
    for (const WycheproofTestVector& test_vector :
         ReadWycheproofTestVectors("xchacha20_poly1305_test.json")) {
      params.push_back({/*test_name=*/"Xchacha20Poly1305",
                        /*cipher_name=*/CipherType::kXchacha20Poly1305,
                        /*nonce_size=*/24,
                        /*tag_size=*/16,
                        /*key_sizes=*/{32}, test_vector});
    }
  }
  return params;
}

INSTANTIATE_TEST_SUITE_P(
    SslOneShotAeadWycheproofTests, SslOneShotAeadWycheproofTest,
    ValuesIn(GetWycheproofTestParams()),
    [](const TestParamInfo<SslOneShotAeadWycheproofTest::ParamType>& info) {
      return absl::StrCat(info.param.test_name, "Tid",
                          info.param.test_vector.id);
    });

}  // namespace
}  // namespace internal
}  // namespace tink
}  // namespace crypto
