/*
* (C) 2014,2015,2017 Jack Lloyd
* (C) 2016 Daniel Neus, Rohde & Schwarz Cybersecurity
* (C) 2018 Ribose Inc
*
* Botan is released under the Simplified BSD License (see license.txt)
*/

#include "tests.h"

#if defined(BOTAN_HAS_CIPHER_MODES)
   #include <botan/cipher_mode.h>
   #include <botan/exceptn.h>
   #include <botan/hex.h>
   #include <botan/rng.h>
#endif

namespace Botan_Tests {

namespace {

#if defined(BOTAN_HAS_CIPHER_MODES)

class Cipher_Mode_Tests final : public Text_Based_Test {
   public:
      Cipher_Mode_Tests() : Text_Based_Test("modes", "Key,Nonce,In,Out") {}

      std::vector<std::string> possible_providers(const std::string& algo) override {
         return provider_filter(Botan::Cipher_Mode::providers(algo));
      }

      Test::Result run_one_test(const std::string& algo, const VarMap& vars) override {
         const std::vector<uint8_t> key = vars.get_req_bin("Key");
         const std::vector<uint8_t> nonce = vars.get_req_bin("Nonce");
         const std::vector<uint8_t> input = vars.get_req_bin("In");
         const std::vector<uint8_t> expected = vars.get_req_bin("Out");

         Test::Result result(algo);

         const std::vector<std::string> providers = possible_providers(algo);

         if(providers.empty()) {
            result.note_missing("cipher mode " + algo);
            return result;
         }

         for(auto&& provider_ask : providers) {
            auto enc = Botan::Cipher_Mode::create(algo, Botan::Cipher_Dir::Encryption, provider_ask);

            auto dec = Botan::Cipher_Mode::create(algo, Botan::Cipher_Dir::Decryption, provider_ask);

            if(!enc || !dec) {
               if(enc) {
                  result.test_failure("Provider " + provider_ask + " has encrypt but not decrypt");
               }
               if(dec) {
                  result.test_failure("Provider " + provider_ask + " has decrypt but not encrypt");
               }
               result.note_missing(algo);
               return result;
            }

            result.test_sz_eq(
               "enc and dec granularity is the same", enc->update_granularity(), dec->update_granularity());

            result.test_sz_gt("update granularity is non-zero", enc->update_granularity(), 0);

            result.test_sz_eq(
               "enc and dec ideal granularity is the same", enc->ideal_granularity(), dec->ideal_granularity());

            result.test_sz_gt(
               "ideal granularity is at least update granularity", enc->ideal_granularity(), enc->update_granularity());

            result.test_is_true("ideal granularity is a multiple of update granularity",
                                enc->ideal_granularity() % enc->update_granularity() == 0);

            try {
               test_mode(result, algo, provider_ask, "encryption", *enc, key, nonce, input, expected, this->rng());
            } catch(Botan::Exception& e) {
               result.test_failure("Encryption tests failed", e.what());
            }

            try {
               // NOLINTNEXTLINE(*-suspicious-call-argument) intentionally swapping ptext and ctext arguments here
               test_mode(result, algo, provider_ask, "decryption", *dec, key, nonce, expected, input, this->rng());
            } catch(Botan::Exception& e) {
               result.test_failure("Decryption tests failed", e.what());
            }
         }

         return result;
      }

   private:
      static void test_mode(Test::Result& result,
                            const std::string& algo,
                            const std::string& provider,
                            const std::string& direction,
                            Botan::Cipher_Mode& mode,
                            const std::vector<uint8_t>& key,
                            const std::vector<uint8_t>& nonce,
                            const std::vector<uint8_t>& input,
                            const std::vector<uint8_t>& expected,
                            Botan::RandomNumberGenerator& rng) {
         const bool is_cbc = (algo.find("/CBC") != std::string::npos);
         const bool is_ctr = (algo.find("CTR") != std::string::npos);

         result.test_str_eq("name", mode.name(), algo);

         // Some modes report base even if got from another provider
         if(mode.provider() != "base") {
            result.test_str_eq("provider", mode.provider(), provider);
         }

         result.test_is_false("mode not authenticated", mode.authenticated());

         const size_t update_granularity = mode.update_granularity();
         const size_t min_final_bytes = mode.minimum_final_size();

         // FFI currently requires this, so assure it is true for all modes
         result.test_sz_gt("buffer sizes ok", mode.ideal_granularity(), min_final_bytes);

         result.test_is_false("key not set", mode.has_keying_material());

         result.test_throws<Botan::Invalid_State>("Unkeyed object throws", [&]() {
            Botan::secure_vector<uint8_t> bad(min_final_bytes);
            mode.finish(bad);
         });

         if(is_cbc) {
            // can't test equal due to CBC padding

            if(direction == "encryption") {
               result.test_sz_lte("output_length", mode.output_length(input.size()), expected.size());
            } else {
               result.test_sz_gte("output_length", mode.output_length(input.size()), expected.size());
            }
         } else {
            // assume all other modes are not expanding (currently true)
            result.test_sz_eq("output_length", mode.output_length(input.size()), expected.size());
         }

         result.test_is_true("default nonce size is allowed", mode.valid_nonce_length(mode.default_nonce_length()));

         // Test that disallowed nonce sizes result in an exception
         static constexpr size_t large_nonce_size = 65000;
         result.test_is_false("Large nonce not allowed", mode.valid_nonce_length(large_nonce_size));
         result.test_throws<Botan::Invalid_Argument>("Large nonce causes exception",
                                                     [&mode]() { mode.start(nullptr, large_nonce_size); });

         Botan::secure_vector<uint8_t> garbage = rng.random_vec(update_granularity);
         Botan::secure_vector<uint8_t> ultimate_garbage = rng.random_vec(min_final_bytes);

         // Test to make sure reset() resets what we need it to
         result.test_throws<Botan::Invalid_State>("Cannot process data (update) until key is set",
                                                  [&]() { mode.update(garbage); });
         result.test_throws<Botan::Invalid_State>("Cannot process data (finish) until key is set",
                                                  [&]() { mode.finish(ultimate_garbage); });

         mode.set_key(mutate_vec(key, rng));

         if(!is_ctr) {
            result.test_throws<Botan::Invalid_State>("Cannot process data until nonce is set",
                                                     [&]() { mode.update(garbage); });

            // Regression: finish_msg without start_msg must throw before
            // touching the user buffer, even on input sizes that hit a
            // mode's partial-block / ciphertext-stealing path.
            Botan::secure_vector<uint8_t> partial(min_final_bytes + update_granularity + 1, 0xAB);
            const auto partial_orig = partial;
            result.test_throws<Botan::Invalid_State>("finish without start throws on partial-block input",
                                                     [&]() { mode.finish(partial); });
            result.test_bin_eq("buffer unmodified when finish throws on no-start", partial, partial_orig);
         }

         mode.start(mutate_vec(nonce, rng));
         mode.reset();

         if(!is_ctr) {
            result.test_throws<Botan::Invalid_State>("Cannot process data until nonce is set (after start/reset)",
                                                     [&]() { mode.update(garbage); });
         }

         mode.start(mutate_vec(nonce, rng));
         mode.update(garbage);

         mode.reset();

         mode.set_key(key);
         result.test_is_true("key is set", mode.has_keying_material());
         mode.start(nonce);

         Botan::secure_vector<uint8_t> buf;

         buf.assign(input.begin(), input.end());
         mode.finish(buf);
         result.test_bin_eq(direction + " all-in-one", buf, expected);

         // Test finish() with non-zero offset
         {
            const size_t test_offset = 1 + rng.next_byte() % 32;
            buf.assign(test_offset, 0xAB);
            buf.insert(buf.end(), input.begin(), input.end());

            mode.start(nonce);
            mode.finish(buf, test_offset);

            for(size_t i = 0; i < test_offset; ++i) {
               result.test_u8_eq(direction + " prefix byte", buf[i], 0xAB);
            }
            result.test_bin_eq(direction + " finish with offset", std::span{buf}.subspan(test_offset), expected);
         }

         // Test update() + finish() with non-zero offset
         if(input.size() >= update_granularity + min_final_bytes) {
            const size_t test_offset = 1 + rng.next_byte() % 32;
            const size_t max_blocks = (input.size() - min_final_bytes) / update_granularity;
            const size_t bytes_to_update = max_blocks * update_granularity;

            buf.assign(test_offset, 0xAB);
            buf.insert(buf.end(), input.begin(), input.begin() + bytes_to_update);

            Botan::secure_vector<uint8_t> final_buf(test_offset, 0xAB);
            final_buf.insert(final_buf.end(), input.begin() + bytes_to_update, input.end());

            mode.start(nonce);
            mode.update(buf, test_offset);
            mode.finish(final_buf, test_offset);

            for(size_t i = 0; i < test_offset; ++i) {
               result.test_u8_eq(direction + " update offset prefix byte", buf[i], 0xAB);
               result.test_u8_eq(direction + " finish offset prefix byte", final_buf[i], 0xAB);
            }

            Botan::secure_vector<uint8_t> combined;
            combined.insert(combined.end(), buf.begin() + test_offset, buf.end());
            combined.insert(combined.end(), final_buf.begin() + test_offset, final_buf.end());
            result.test_bin_eq(direction + " update+finish with offset", combined, expected);
         }

         // additionally test update() and process() if possible
         if(input.size() >= update_granularity + min_final_bytes) {
            const size_t max_blocks_to_process = (input.size() - min_final_bytes) / update_granularity;
            const size_t bytes_to_process = max_blocks_to_process * update_granularity;

            // test update, 1 block at a time
            if(max_blocks_to_process > 1) {
               Botan::secure_vector<uint8_t> block(update_granularity);
               buf.clear();

               mode.start(nonce);
               for(size_t i = 0; i != max_blocks_to_process; ++i) {
                  block.assign(input.data() + i * update_granularity, input.data() + (i + 1) * update_granularity);

                  mode.update(block);
                  buf += block;
               }

               Botan::secure_vector<uint8_t> last_bits(input.data() + bytes_to_process, input.data() + input.size());
               mode.finish(last_bits);
               buf += last_bits;

               result.test_bin_eq(direction + " update-1", buf, expected);
            }

            // test update with maximum length input
            buf.assign(input.data(), input.data() + bytes_to_process);
            Botan::secure_vector<uint8_t> last_bits(input.data() + bytes_to_process, input.data() + input.size());

            mode.start(nonce);
            mode.update(buf);
            mode.finish(last_bits);

            buf += last_bits;

            result.test_bin_eq(direction + " update-all", buf, expected);

            // test process with maximum length input
            mode.start(nonce);
            buf.assign(input.begin(), input.end());

            const size_t bytes_written = mode.process(buf.data(), bytes_to_process);

            result.test_sz_eq("correct number of bytes processed", bytes_written, bytes_to_process);

            mode.finish(buf, bytes_to_process);
            result.test_bin_eq(direction + " process", buf, expected);
         }

         // Regression: re-keying must drop any prior message state so the
         // user can't accidentally process data under the new key with
         // leftover IV/feedback/tweak from the previous key.
         if(!is_ctr) {
            mode.set_key(key);
            mode.start(nonce);
            mode.update(garbage);
            mode.set_key(mutate_vec(key, rng));
            result.test_throws<Botan::Invalid_State>("Cannot process data after re-key without restart",
                                                     [&]() { mode.update(garbage); });
         }

         mode.clear();
         result.test_is_false("key is not set", mode.has_keying_material());

         result.test_throws<Botan::Invalid_State>("Unkeyed object throws after clear", [&]() {
            Botan::secure_vector<uint8_t> bad(min_final_bytes);
            mode.finish(bad);
         });
      }
};

BOTAN_REGISTER_SMOKE_TEST("modes", "cipher_modes", Cipher_Mode_Tests);

class Cipher_Mode_IV_Carry_Tests final : public Test {
   public:
      std::vector<Test::Result> run() override {
         std::vector<Test::Result> results;
         results.push_back(test_cbc_iv_carry());
         results.push_back(test_cfb_iv_carry());
         results.push_back(test_ctr_iv_carry());
         return results;
      }

   private:
      static Test::Result test_cbc_iv_carry() {
         Test::Result result("CBC IV carry");

   #if defined(BOTAN_HAS_MODE_CBC) && defined(BOTAN_HAS_AES)
         std::unique_ptr<Botan::Cipher_Mode> enc(
            Botan::Cipher_Mode::create("AES-128/CBC/PKCS7", Botan::Cipher_Dir::Encryption));
         std::unique_ptr<Botan::Cipher_Mode> dec(
            Botan::Cipher_Mode::create("AES-128/CBC/PKCS7", Botan::Cipher_Dir::Decryption));

         const std::vector<uint8_t> key(16, 0xAA);
         const std::vector<uint8_t> iv(16, 0xAA);

         Botan::secure_vector<uint8_t> msg1 =
            Botan::hex_decode_locked("446F6E27742075736520706C61696E20434243206D6F6465");
         Botan::secure_vector<uint8_t> msg2 = Botan::hex_decode_locked("49562063617272796F766572");
         Botan::secure_vector<uint8_t> msg3 = Botan::hex_decode_locked("49562063617272796F76657232");

         enc->set_key(key);
         dec->set_key(key);

         enc->start(iv);
         enc->finish(msg1);
         result.test_bin_eq(
            "First ciphertext", msg1, "9BDD7300E0CB61CA71FFF957A71605DB6836159C36781246A1ADF50982757F4B");

         enc->start();
         enc->finish(msg2);

         result.test_bin_eq("Second ciphertext", msg2, "AA8D682958A4A044735DAC502B274DB2");

         enc->start();
         enc->finish(msg3);

         result.test_bin_eq("Third ciphertext", msg3, "1241B9976F73051BCF809525D6E86C25");

         dec->start(iv);
         dec->finish(msg1);

         dec->start();
         dec->finish(msg2);

         dec->start();
         dec->finish(msg3);
         result.test_bin_eq("Third plaintext", msg3, "49562063617272796F76657232");

   #endif
         return result;
      }

      static Test::Result test_cfb_iv_carry() {
         Test::Result result("CFB IV carry");
   #if defined(BOTAN_HAS_MODE_CFB) && defined(BOTAN_HAS_AES)
         std::unique_ptr<Botan::Cipher_Mode> enc(
            Botan::Cipher_Mode::create("AES-128/CFB(8)", Botan::Cipher_Dir::Encryption));
         std::unique_ptr<Botan::Cipher_Mode> dec(
            Botan::Cipher_Mode::create("AES-128/CFB(8)", Botan::Cipher_Dir::Decryption));

         const std::vector<uint8_t> key(16, 0xAA);
         const std::vector<uint8_t> iv(16, 0xAB);

         Botan::secure_vector<uint8_t> msg1 = Botan::hex_decode_locked("ABCDEF01234567");
         Botan::secure_vector<uint8_t> msg2 = Botan::hex_decode_locked("0000123456ABCDEF");
         Botan::secure_vector<uint8_t> msg3 = Botan::hex_decode_locked("012345");

         enc->set_key(key);
         dec->set_key(key);

         enc->start(iv);
         enc->finish(msg1);
         result.test_bin_eq("First ciphertext", msg1, "a51522387c4c9b");

         enc->start();
         enc->finish(msg2);

         result.test_bin_eq("Second ciphertext", msg2, "105457dc2e0649d4");

         enc->start();
         enc->finish(msg3);

         result.test_bin_eq("Third ciphertext", msg3, "53bd65");

         dec->start(iv);
         dec->finish(msg1);
         result.test_bin_eq("First plaintext", msg1, "ABCDEF01234567");

         dec->start();
         dec->finish(msg2);
         result.test_bin_eq("Second plaintext", msg2, "0000123456ABCDEF");

         dec->start();
         dec->finish(msg3);
         result.test_bin_eq("Third plaintext", msg3, "012345");
   #endif
         return result;
      }

      static Test::Result test_ctr_iv_carry() {
         Test::Result result("CTR IV carry");
   #if defined(BOTAN_HAS_CTR_BE) && defined(BOTAN_HAS_AES)

         std::unique_ptr<Botan::Cipher_Mode> enc(
            Botan::Cipher_Mode::create("AES-128/CTR-BE", Botan::Cipher_Dir::Encryption));
         std::unique_ptr<Botan::Cipher_Mode> dec(
            Botan::Cipher_Mode::create("AES-128/CTR-BE", Botan::Cipher_Dir::Decryption));

         const std::vector<uint8_t> key = Botan::hex_decode("2B7E151628AED2A6ABF7158809CF4F3C");
         const std::vector<uint8_t> iv = Botan::hex_decode("F0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF");

         enc->set_key(key);
         dec->set_key(key);

         const std::vector<std::string> exp_ciphertext = {
            "EC",
            "8CDF",
            "739860",
            "7CB0F2D2",
            "1675EA9EA1",
            "E4362B7C3C67",
            "73516318A077D7",
            "FC5073AE6A2CC378",
            "7889374FBEB4C81B17",
            "BA6C44E89C399FF0F198C",
         };

         for(size_t i = 1; i != 10; ++i) {
            if(i == 1) {
               enc->start(iv);
               dec->start(iv);
            } else {
               enc->start();
               dec->start();
            }

            Botan::secure_vector<uint8_t> msg(i, 0);
            enc->finish(msg);

            result.test_bin_eq("Ciphertext", msg, exp_ciphertext[i - 1]);

            dec->finish(msg);

            for(const uint8_t b : msg) {
               result.test_u8_eq("Plaintext zeros", b, 0);
            }
         }
   #endif
         return result;
      }
};

BOTAN_REGISTER_TEST("modes", "iv_carryover", Cipher_Mode_IV_Carry_Tests);

class CBC_Invalid_Padding_Test final : public Test {
   public:
      std::vector<Test::Result> run() override {
         Test::Result result("CBC invalid padding");

   #if defined(BOTAN_HAS_MODE_CBC) && defined(BOTAN_HAS_AES)
         auto enc = Botan::Cipher_Mode::create_or_throw("AES-128/CBC/NoPadding", Botan::Cipher_Dir::Encryption);
         auto dec = Botan::Cipher_Mode::create_or_throw("AES-128/CBC/PKCS7", Botan::Cipher_Dir::Decryption);

         const std::vector<uint8_t> key(16, 0xAB);
         const std::vector<uint8_t> iv(16, 0xCD);

         // A final byte of 0x00 is never valid PKCS#7 padding
         Botan::secure_vector<uint8_t> buf(32, 0x42);
         buf[31] = 0x00;

         enc->set_key(key);
         enc->start(iv);
         enc->finish(buf);

         dec->set_key(key);
         dec->start(iv);

         result.test_throws<Botan::Decoding_Error>("invalid CBC padding is rejected", [&]() { dec->finish(buf); });

         // The decrypted but unverified plaintext must have been zeroed
         result.test_sz_eq("buffer size unchanged after padding failure", buf.size(), 32);
         uint8_t accum = 0;
         for(const uint8_t b : buf) {
            accum |= b;
         }
         result.test_is_true("plaintext zeroed after padding failure", accum == 0);
   #endif

         return {result};
      }
};

BOTAN_REGISTER_TEST("modes", "cbc_invalid_padding", CBC_Invalid_Padding_Test);

#endif

}  // namespace

}  // namespace Botan_Tests
