// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

#if !defined(DART_IO_SECURE_SOCKET_DISABLED)

#include "platform/globals.h"
#if defined(DART_HOST_OS_WINDOWS)

#include "bin/security_context.h"

#include <openssl/bio.h>
#include <openssl/ssl.h>
#include <openssl/x509.h>
#include <wincrypt.h>

#include "bin/directory.h"
#include "bin/file.h"
#include "bin/secure_socket_filter.h"
#include "bin/secure_socket_utils.h"
#include "platform/syslog.h"

#ifndef DART_TARGET_OS_WINDOWS_UWP
#pragma comment(lib, "crypt32.lib")
#endif

namespace dart {
namespace bin {

const intptr_t SSLCertContext::kApproximateSize = sizeof(SSLCertContext);

static void PrintSSLErr(const char* str) {
  int error = ERR_get_error();
  char error_string[SecureSocketUtils::SSL_ERROR_MESSAGE_BUFFER_SIZE];
  ERR_error_string_n(error, error_string,
                     SecureSocketUtils::SSL_ERROR_MESSAGE_BUFFER_SIZE);
  Syslog::PrintErr("%s %s\n", str, error_string);
}

#ifndef DART_TARGET_OS_WINDOWS_UWP
static bool AddCertificatesFromNamedSystemStore(const wchar_t* name,
                                                DWORD store_type,
                                                X509_STORE* store) {
  ASSERT(store_type == CERT_SYSTEM_STORE_CURRENT_USER ||
         store_type == CERT_SYSTEM_STORE_LOCAL_MACHINE);

  if (SSL_LOG_STATUS) {
    Syslog::Print("AddCertificatesFromNamedSystemStore %ls type: %s\n", name,
                  store_type == CERT_SYSTEM_STORE_CURRENT_USER
                      ? "Current User"
                      : "Local Machine");
  }

  HCERTSTORE cert_store =
      CertOpenStore(CERT_STORE_PROV_SYSTEM,
                    0,     // the encoding type is not needed
                    NULL,  // use the default HCRYPTPROV
                    store_type | CERT_STORE_READONLY_FLAG, name);

  if (cert_store == nullptr) {
    if (SSL_LOG_STATUS) {
      DWORD error = GetLastError();
      Syslog::PrintErr(
          "Failed to open Windows root store %ls type %d due to %d\n", name,
          store_type, error);
    }
    return false;
  }

  // Iterating through all certificates in the store. A nullptr is required to
  // start iteration.
  PCCERT_CONTEXT cert_context = nullptr;
  do {
    cert_context = CertEnumCertificatesInStore(cert_store, cert_context);
    if (cert_context == nullptr) {
      // reach the end of store.
      break;
    }
    BIO* root_cert_bio =
        BIO_new_mem_buf(const_cast<unsigned char*>(cert_context->pbCertEncoded),
                        cert_context->cbCertEncoded);
    // `root_cert` has to be initialized to nullptr, otherwise, it will be
    // considered as an existing X509 and cause segmentation fault.
    X509* root_cert = nullptr;
    if (d2i_X509_bio(root_cert_bio, &root_cert) == nullptr) {
      if (SSL_LOG_STATUS) {
        PrintSSLErr("Fail to read certificate");
      }
      BIO_free(root_cert_bio);
      continue;
    }
    BIO_free(root_cert_bio);

    if (SSL_LOG_STATUS) {
      auto s_name = X509_get_subject_name(root_cert);
      auto s_issuer_name = X509_get_issuer_name(root_cert);
      auto serial_number = X509_get_serialNumber(root_cert);
      BIGNUM* bn = ASN1_INTEGER_to_BN(serial_number, nullptr);
      char* hex = BN_bn2hex(bn);
      Syslog::Print("Considering root certificate serial: %s subject name: ",
                    hex);
      OPENSSL_free(hex);
      X509_NAME_print_ex_fp(stdout, s_name, 4, 0);
      Syslog::Print(" issuer:");
      X509_NAME_print_ex_fp(stdout, s_issuer_name, 4, 0);
      Syslog::Print("\n");
    }

    if (!SecureSocketUtils::IsCurrentTimeInsideCertValidDateRange(root_cert)) {
      if (SSL_LOG_STATUS) {
        Syslog::Print("...certificate is outside of its valid date range\n");
      }
      X509_free(root_cert);
      continue;
    }

    int status = X509_STORE_add_cert(store, root_cert);
    // Always free the certificate after use.
    X509_free(root_cert);
    if (status == 0) {
      int error = ERR_get_error();
      if (ERR_GET_REASON(error) == X509_R_CERT_ALREADY_IN_HASH_TABLE) {
        if (SSL_LOG_STATUS) {
          Syslog::Print("...duplicate\n");
        }
        continue;
      }
      if (SSL_LOG_STATUS) {
        PrintSSLErr("Failed to add certificate to x509 trust store");
      }
      CertFreeCertificateContext(cert_context);
      CertCloseStore(cert_store, 0);
      return false;
    }
  } while (cert_context != nullptr);

  // It always returns non-zero.
  CertFreeCertificateContext(cert_context);
  if (!CertCloseStore(cert_store, 0)) {
    if (SSL_LOG_STATUS) {
      PrintSSLErr("Fail to close system root store");
    }
    return false;
  }
  return true;
}

static bool AddCertificatesFromSystemStore(DWORD store_type,
                                           X509_STORE* store) {
  if (!AddCertificatesFromNamedSystemStore(L"ROOT", store_type, store)) {
    return false;
  }
  if (!AddCertificatesFromNamedSystemStore(L"CA", store_type, store)) {
    return false;
  }
  if (!AddCertificatesFromNamedSystemStore(L"TRUST", store_type, store)) {
    return false;
  }
  if (!AddCertificatesFromNamedSystemStore(L"MY", store_type, store)) {
    return false;
  }
  return true;
}
#endif  // ifdef DART_TARGET_OS_WINDOWS_UWP

// Add certificates from Windows trusted root store.
static bool AddCertificatesFromRootStore(X509_STORE* store) {
// The UWP platform doesn't support CertEnumCertificatesInStore hence
// this function cannot work when compiled in UWP mode.
#ifdef DART_TARGET_OS_WINDOWS_UWP
  return false;
#else
  if (!AddCertificatesFromSystemStore(CERT_SYSTEM_STORE_CURRENT_USER, store)) {
    return false;
  }

  if (!AddCertificatesFromSystemStore(CERT_SYSTEM_STORE_LOCAL_MACHINE, store)) {
    return false;
  }

  return true;
#endif  // ifdef DART_TARGET_OS_WINDOWS_UWP
}

void SSLCertContext::TrustBuiltinRoots() {
  // First, try to use locations specified on the command line.
  if (root_certs_file() != nullptr) {
    LoadRootCertFile(root_certs_file());
    return;
  }
  if (root_certs_cache() != nullptr) {
    LoadRootCertCache(root_certs_cache());
    return;
  }

  if (bypass_trusting_system_roots()) {
    if (SSL_LOG_STATUS) {
      Syslog::Print("Bypass trusting Windows built-in roots\n");
    }
  } else {
    if (SSL_LOG_STATUS) {
      Syslog::Print("Trusting Windows built-in roots\n");
    }
    X509_STORE* store = SSL_CTX_get_cert_store(context());
    if (AddCertificatesFromRootStore(store)) {
      return;
    }
  }
  // Reset store. SSL_CTX_set_cert_store will take ownership of store. A manual
  // free is not needed.
  SSL_CTX_set_cert_store(context(), X509_STORE_new());
}

void SSLCertContext::RegisterCallbacks(SSL* ssl) {
  // No callbacks to register for implementations using BoringSSL's built-in
  // verification mechanism.
}

TrustEvaluateHandlerFunc SSLCertContext::GetTrustEvaluateHandler() {
  return nullptr;
}

}  // namespace bin
}  // namespace dart

#endif  // defined(DART_HOST_OS_WINDOWS)

#endif  // !defined(DART_IO_SECURE_SOCKET_DISABLED)
