/*
* (C) 2015,2017,2018 Jack Lloyd
*
* Botan is released under the Simplified BSD License (see license.txt)
*/

#include <botan/ffi.h>
#include <botan/internal/ffi_util.h>
#include <botan/internal/ffi_pkey.h>

#if defined(BOTAN_HAS_X509_CERTIFICATES)
   #include <botan/x509cert.h>
   #include <botan/x509path.h>
   #include <botan/x509_crl.h>
   #include <botan/data_src.h>
#endif

extern "C" {

using namespace Botan_FFI;

#if defined(BOTAN_HAS_X509_CERTIFICATES)

BOTAN_FFI_DECLARE_STRUCT(botan_x509_cert_struct, Botan::X509_Certificate, 0x8F628937);

#endif

int botan_x509_cert_load_file(botan_x509_cert_t* cert_obj, const char* cert_path)
   {
   if(!cert_obj || !cert_path)
      return BOTAN_FFI_ERROR_NULL_POINTER;

#if defined(BOTAN_HAS_X509_CERTIFICATES) && defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)

   return ffi_guard_thunk(__func__, [=]() -> int {
      std::unique_ptr<Botan::X509_Certificate> c(new Botan::X509_Certificate(cert_path));
      *cert_obj = new botan_x509_cert_struct(c.release());
      return BOTAN_FFI_SUCCESS;
      });

#else
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
   }

int botan_x509_cert_dup(botan_x509_cert_t* cert_obj, botan_x509_cert_t cert)
   {
   if(!cert_obj)
      return BOTAN_FFI_ERROR_NULL_POINTER;

#if defined(BOTAN_HAS_X509_CERTIFICATES) && defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)

   return ffi_guard_thunk(__func__, [=]() -> int {
      std::unique_ptr<Botan::X509_Certificate> c(new Botan::X509_Certificate(safe_get(cert)));
      *cert_obj = new botan_x509_cert_struct(c.release());
      return BOTAN_FFI_SUCCESS;
      });

#else
   BOTAN_UNUSED(cert);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
   }

int botan_x509_cert_load(botan_x509_cert_t* cert_obj, const uint8_t cert_bits[], size_t cert_bits_len)
   {
   if(!cert_obj || !cert_bits)
      return BOTAN_FFI_ERROR_NULL_POINTER;

#if defined(BOTAN_HAS_X509_CERTIFICATES)
   return ffi_guard_thunk(__func__, [=]() -> int {
      Botan::DataSource_Memory bits(cert_bits, cert_bits_len);
      std::unique_ptr<Botan::X509_Certificate> c(new Botan::X509_Certificate(bits));
      *cert_obj = new botan_x509_cert_struct(c.release());
      return BOTAN_FFI_SUCCESS;
      });
#else
   BOTAN_UNUSED(cert_bits_len);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
   }

int botan_x509_cert_get_public_key(botan_x509_cert_t cert, botan_pubkey_t* key)
   {
   if(key == nullptr)
      return BOTAN_FFI_ERROR_NULL_POINTER;

   *key = nullptr;

#if defined(BOTAN_HAS_X509_CERTIFICATES)
   return ffi_guard_thunk(__func__, [=]() -> int {
      std::unique_ptr<Botan::Public_Key> publicKey = safe_get(cert).load_subject_public_key();
      *key = new botan_pubkey_struct(publicKey.release());
      return BOTAN_FFI_SUCCESS;
      });
#else
   BOTAN_UNUSED(cert);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
   }

int botan_x509_cert_get_issuer_dn(botan_x509_cert_t cert,
                                  const char* key, size_t index,
                                  uint8_t out[], size_t* out_len)
   {
#if defined(BOTAN_HAS_X509_CERTIFICATES)
   return BOTAN_FFI_DO(Botan::X509_Certificate, cert, c, { return write_str_output(out, out_len, c.issuer_info(key).at(index)); });
#else
   BOTAN_UNUSED(cert, key, index, out, out_len);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
   }

int botan_x509_cert_get_subject_dn(botan_x509_cert_t cert,
                                   const char* key, size_t index,
                                   uint8_t out[], size_t* out_len)
   {
#if defined(BOTAN_HAS_X509_CERTIFICATES)
   return BOTAN_FFI_DO(Botan::X509_Certificate, cert, c, { return write_str_output(out, out_len, c.subject_info(key).at(index)); });
#else
   BOTAN_UNUSED(cert, key, index, out, out_len);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
   }

int botan_x509_cert_to_string(botan_x509_cert_t cert, char out[], size_t* out_len)
   {
#if defined(BOTAN_HAS_X509_CERTIFICATES)
   return BOTAN_FFI_DO(Botan::X509_Certificate, cert, c, { return write_str_output(out, out_len, c.to_string()); });
#else
   BOTAN_UNUSED(cert, out, out_len);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
   }

int botan_x509_cert_allowed_usage(botan_x509_cert_t cert, unsigned int key_usage)
   {
#if defined(BOTAN_HAS_X509_CERTIFICATES)
   return BOTAN_FFI_RETURNING(Botan::X509_Certificate, cert, c, {
      const Botan::Key_Constraints k = static_cast<Botan::Key_Constraints>(key_usage);
      if(c.allowed_usage(k))
         return BOTAN_FFI_SUCCESS;
      return 1;
      });
#else
   BOTAN_UNUSED(cert, key_usage);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
   }

int botan_x509_cert_destroy(botan_x509_cert_t cert)
   {
#if defined(BOTAN_HAS_X509_CERTIFICATES)
   return BOTAN_FFI_CHECKED_DELETE(cert);
#else
   BOTAN_UNUSED(cert);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
   }

int botan_x509_cert_get_time_starts(botan_x509_cert_t cert, char out[], size_t* out_len)
   {
#if defined(BOTAN_HAS_X509_CERTIFICATES)
   return BOTAN_FFI_DO(Botan::X509_Certificate, cert, c, { return write_str_output(out, out_len, c.not_before().to_string()); });
#else
   BOTAN_UNUSED(cert, out, out_len);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
   }

int botan_x509_cert_get_time_expires(botan_x509_cert_t cert, char out[], size_t* out_len)
   {
#if defined(BOTAN_HAS_X509_CERTIFICATES)
   return BOTAN_FFI_DO(Botan::X509_Certificate, cert, c, { return write_str_output(out, out_len, c.not_after().to_string()); });
#else
   BOTAN_UNUSED(cert, out, out_len);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
   }

int botan_x509_cert_not_before(botan_x509_cert_t cert, uint64_t* time_since_epoch)
   {
#if defined(BOTAN_HAS_X509_CERTIFICATES)
   return BOTAN_FFI_DO(Botan::X509_Certificate, cert, c, {
      *time_since_epoch = c.not_before().time_since_epoch();
      });
#else
   BOTAN_UNUSED(cert, time_since_epoch);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
   }

int botan_x509_cert_not_after(botan_x509_cert_t cert, uint64_t* time_since_epoch)
   {
#if defined(BOTAN_HAS_X509_CERTIFICATES)
   return BOTAN_FFI_DO(Botan::X509_Certificate, cert, c, {
      *time_since_epoch = c.not_after().time_since_epoch();
      });
#else
   BOTAN_UNUSED(cert, time_since_epoch);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
   }

int botan_x509_cert_get_serial_number(botan_x509_cert_t cert, uint8_t out[], size_t* out_len)
   {
#if defined(BOTAN_HAS_X509_CERTIFICATES)
   return BOTAN_FFI_DO(Botan::X509_Certificate, cert, c, { return write_vec_output(out, out_len, c.serial_number()); });
#else
   BOTAN_UNUSED(cert, out, out_len);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
   }

int botan_x509_cert_get_fingerprint(botan_x509_cert_t cert, const char* hash, uint8_t out[], size_t* out_len)
   {
#if defined(BOTAN_HAS_X509_CERTIFICATES)
   return BOTAN_FFI_DO(Botan::X509_Certificate, cert, c, { return write_str_output(out, out_len, c.fingerprint(hash)); });
#else
   BOTAN_UNUSED(cert, hash, out, out_len);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
   }

int botan_x509_cert_get_authority_key_id(botan_x509_cert_t cert, uint8_t out[], size_t* out_len)
   {
#if defined(BOTAN_HAS_X509_CERTIFICATES)
   return BOTAN_FFI_DO(Botan::X509_Certificate, cert, c, { return write_vec_output(out, out_len, c.authority_key_id()); });
#else
   BOTAN_UNUSED(cert, out, out_len);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
   }

int botan_x509_cert_get_subject_key_id(botan_x509_cert_t cert, uint8_t out[], size_t* out_len)
   {
#if defined(BOTAN_HAS_X509_CERTIFICATES)
   return BOTAN_FFI_DO(Botan::X509_Certificate, cert, c, { return write_vec_output(out, out_len, c.subject_key_id()); });
#else
   BOTAN_UNUSED(cert, out, out_len);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
   }

int botan_x509_cert_get_public_key_bits(botan_x509_cert_t cert, uint8_t out[], size_t* out_len)
   {
#if defined(BOTAN_HAS_X509_CERTIFICATES)
   return BOTAN_FFI_DO(Botan::X509_Certificate, cert, c, { return write_vec_output(out, out_len, c.subject_public_key_bits()); });
#else
   BOTAN_UNUSED(cert, out, out_len);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
   }

int botan_x509_cert_hostname_match(botan_x509_cert_t cert, const char* hostname)
   {
   if(hostname == nullptr)
      return BOTAN_FFI_ERROR_NULL_POINTER;

#if defined(BOTAN_HAS_X509_CERTIFICATES)
   return BOTAN_FFI_DO(Botan::X509_Certificate, cert, c,
                       { return c.matches_dns_name(hostname) ? 0 : -1; });
#else
   BOTAN_UNUSED(cert);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
   }

int botan_x509_cert_verify(int* result_code,
                           botan_x509_cert_t cert,
                           const botan_x509_cert_t* intermediates,
                           size_t intermediates_len,
                           const botan_x509_cert_t* trusted,
                           size_t trusted_len,
                           const char* trusted_path,
                           size_t required_strength,
                           const char* hostname_cstr,
                           uint64_t reference_time)
   {
   if(required_strength == 0)
      required_strength = 110;

#if defined(BOTAN_HAS_X509_CERTIFICATES)
   return ffi_guard_thunk(__func__, [=]() -> int {
      const std::string hostname((hostname_cstr == nullptr) ? "" : hostname_cstr);
      const Botan::Usage_Type usage = Botan::Usage_Type::UNSPECIFIED;
      const auto validation_time = reference_time == 0 ?
         std::chrono::system_clock::now() :
         std::chrono::system_clock::from_time_t(static_cast<time_t>(reference_time));

      std::vector<Botan::X509_Certificate> end_certs;
      end_certs.push_back(safe_get(cert));
      for(size_t i = 0; i != intermediates_len; ++i)
         end_certs.push_back(safe_get(intermediates[i]));

      std::unique_ptr<Botan::Certificate_Store> trusted_from_path;
      std::unique_ptr<Botan::Certificate_Store_In_Memory> trusted_extra;
      std::vector<Botan::Certificate_Store*> trusted_roots;

      if(trusted_path && *trusted_path)
         {
         trusted_from_path.reset(new Botan::Certificate_Store_In_Memory(trusted_path));
         trusted_roots.push_back(trusted_from_path.get());
         }

      if(trusted_len > 0)
         {
         trusted_extra.reset(new Botan::Certificate_Store_In_Memory);
         for(size_t i = 0; i != trusted_len; ++i)
            {
            trusted_extra->add_certificate(safe_get(trusted[i]));
            }
         trusted_roots.push_back(trusted_extra.get());
         }

      Botan::Path_Validation_Restrictions restrictions(false, required_strength);

      auto validation_result = Botan::x509_path_validate(end_certs,
                                                         restrictions,
                                                         trusted_roots,
                                                         hostname,
                                                         usage,
                                                         validation_time);

      if(result_code)
         *result_code = static_cast<int>(validation_result.result());

      if(validation_result.successful_validation())
         return 0;
      else
         return 1;
      });
#else
   BOTAN_UNUSED(result_code, cert, intermediates, intermediates_len, trusted);
   BOTAN_UNUSED(trusted_len, trusted_path, hostname_cstr, reference_time);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
   }

const char* botan_x509_cert_validation_status(int code)
   {
   if(code < 0)
      return nullptr;

#if defined(BOTAN_HAS_X509_CERTIFICATES)
   Botan::Certificate_Status_Code sc = static_cast<Botan::Certificate_Status_Code>(code);
   return Botan::to_string(sc);
#else
   return nullptr;
#endif
   }

#if defined(BOTAN_HAS_X509_CERTIFICATES)

BOTAN_FFI_DECLARE_STRUCT(botan_x509_crl_struct, Botan::X509_CRL, 0x2C628910);

#endif

int botan_x509_crl_load_file(botan_x509_crl_t* crl_obj, const char* crl_path)
   {
   if(!crl_obj || !crl_path)
      return BOTAN_FFI_ERROR_NULL_POINTER;

#if defined(BOTAN_HAS_X509_CERTIFICATES) && defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)

   return ffi_guard_thunk(__func__, [=]() -> int {
      std::unique_ptr<Botan::X509_CRL> c(new Botan::X509_CRL(crl_path));
      *crl_obj = new botan_x509_crl_struct(c.release());
      return BOTAN_FFI_SUCCESS;
      });

#else
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
   }

int botan_x509_crl_load(botan_x509_crl_t* crl_obj, const uint8_t crl_bits[], size_t crl_bits_len)
   {
   if(!crl_obj || !crl_bits)
      return BOTAN_FFI_ERROR_NULL_POINTER;

#if defined(BOTAN_HAS_X509_CERTIFICATES)
   return ffi_guard_thunk(__func__, [=]() -> int {
      Botan::DataSource_Memory bits(crl_bits, crl_bits_len);
      std::unique_ptr<Botan::X509_CRL> c(new Botan::X509_CRL(bits));
      *crl_obj = new botan_x509_crl_struct(c.release());
      return BOTAN_FFI_SUCCESS;
      });
#else
   BOTAN_UNUSED(crl_bits_len);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
   }

int botan_x509_crl_destroy(botan_x509_crl_t crl)
   {
#if defined(BOTAN_HAS_X509_CERTIFICATES)
   return BOTAN_FFI_CHECKED_DELETE(crl);
#else
   BOTAN_UNUSED(crl);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
   }

int botan_x509_is_revoked(botan_x509_crl_t crl, botan_x509_cert_t cert)
   {
#if defined(BOTAN_HAS_X509_CERTIFICATES)
   return BOTAN_FFI_RETURNING(Botan::X509_CRL, crl, c, {
      return c.is_revoked(safe_get(cert)) ? 0 : -1;
      });
#else
   BOTAN_UNUSED(cert);
   BOTAN_UNUSED(crl);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
   }

int botan_x509_cert_verify_with_crl(
   int* result_code,
   botan_x509_cert_t cert,
   const botan_x509_cert_t* intermediates,
   size_t intermediates_len,
   const botan_x509_cert_t* trusted,
   size_t trusted_len,
   const botan_x509_crl_t* crls,
   size_t crls_len,
   const char* trusted_path,
   size_t required_strength,
   const char* hostname_cstr,
   uint64_t reference_time)
   {
   if(required_strength == 0)
      required_strength = 110;

#if defined(BOTAN_HAS_X509_CERTIFICATES)
   return ffi_guard_thunk(__func__, [=]() -> int {
      const std::string hostname((hostname_cstr == nullptr) ? "" : hostname_cstr);
      const Botan::Usage_Type usage = Botan::Usage_Type::UNSPECIFIED;
      const auto validation_time = reference_time == 0 ?
         std::chrono::system_clock::now() :
         std::chrono::system_clock::from_time_t(static_cast<time_t>(reference_time));

      std::vector<Botan::X509_Certificate> end_certs;
      end_certs.push_back(safe_get(cert));
      for(size_t i = 0; i != intermediates_len; ++i)
         end_certs.push_back(safe_get(intermediates[i]));

      std::unique_ptr<Botan::Certificate_Store> trusted_from_path;
      std::unique_ptr<Botan::Certificate_Store_In_Memory> trusted_extra;
      std::unique_ptr<Botan::Certificate_Store_In_Memory> trusted_crls;
      std::vector<Botan::Certificate_Store*> trusted_roots;

      if(trusted_path && *trusted_path)
         {
         trusted_from_path.reset(new Botan::Certificate_Store_In_Memory(trusted_path));
         trusted_roots.push_back(trusted_from_path.get());
         }

      if(trusted_len > 0)
         {
         trusted_extra.reset(new Botan::Certificate_Store_In_Memory);
         for(size_t i = 0; i != trusted_len; ++i)
            {
            trusted_extra->add_certificate(safe_get(trusted[i]));
            }
         trusted_roots.push_back(trusted_extra.get());
         }

      if(crls_len > 0)
         {
         trusted_crls.reset(new Botan::Certificate_Store_In_Memory);
         for(size_t i = 0; i != crls_len; ++i)
            {
            trusted_crls->add_crl(safe_get(crls[i]));
            }
         trusted_roots.push_back(trusted_crls.get());
         }

      Botan::Path_Validation_Restrictions restrictions(false, required_strength);

      auto validation_result = Botan::x509_path_validate(end_certs,
                                                         restrictions,
                                                         trusted_roots,
                                                         hostname,
                                                         usage,
                                                         validation_time);

      if(result_code)
         *result_code = static_cast<int>(validation_result.result());

      if(validation_result.successful_validation())
         return 0;
      else
         return 1;
      });
#else
   BOTAN_UNUSED(result_code, cert, intermediates, intermediates_len, trusted);
   BOTAN_UNUSED(trusted_len, trusted_path, hostname_cstr, reference_time, crls, crls_len);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
   }

}
