// 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 "bin/security_context.h"

#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/pkcs12.h>
#include <openssl/ssl.h>
#include <openssl/x509.h>

#include "platform/globals.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"

namespace dart {
namespace bin {

const char* SSLCertContext::root_certs_file_ = nullptr;
const char* SSLCertContext::root_certs_cache_ = nullptr;
bool SSLCertContext::long_ssl_cert_evaluation_ = false;
bool SSLCertContext::bypass_trusting_system_roots_ = false;

int SSLCertContext::CertificateCallback(int preverify_ok,
                                        X509_STORE_CTX* store_ctx) {
  if (preverify_ok == 1) {
    return 1;
  }
  Dart_Isolate isolate = Dart_CurrentIsolate();
  if (isolate == nullptr) {
    FATAL("CertificateCallback called with no current isolate\n");
  }
  X509* certificate = X509_STORE_CTX_get_current_cert(store_ctx);
  int ssl_index = SSL_get_ex_data_X509_STORE_CTX_idx();
  SSL* ssl =
      static_cast<SSL*>(X509_STORE_CTX_get_ex_data(store_ctx, ssl_index));
  SSLFilter* filter = static_cast<SSLFilter*>(
      SSL_get_ex_data(ssl, SSLFilter::filter_ssl_index));
  Dart_Handle callback = filter->bad_certificate_callback();
  if (Dart_IsNull(callback)) {
    return 0;
  }

  // Upref since the Dart X509 object may outlive the SecurityContext.
  if (certificate != nullptr) {
    X509_up_ref(certificate);
  }
  Dart_Handle args[1];
  args[0] = X509Helper::WrappedX509Certificate(certificate);
  if (Dart_IsError(args[0])) {
    filter->callback_error = args[0];
    return 0;
  }
  Dart_Handle result = Dart_InvokeClosure(callback, 1, args);
  if (!Dart_IsError(result) && !Dart_IsBoolean(result)) {
    result = Dart_NewUnhandledExceptionError(DartUtils::NewDartIOException(
        "HandshakeException",
        "BadCertificateCallback returned a value that was not a boolean",
        Dart_Null()));
  }
  // See SSLFilter::Handshake for the semantics of filter->callback_error.
  if (Dart_IsError(result) && filter->callback_error == nullptr) {
    filter->callback_error = result;
    return 0;
  }
  return static_cast<int>(DartUtils::GetBooleanValue(result));
}

void SSLCertContext::KeyLogCallback(const SSL* ssl, const char* line) {
  SSLFilter* filter = static_cast<SSLFilter*>(
      SSL_get_ex_data(ssl, SSLFilter::filter_ssl_index));

  Dart_Port port = filter->key_log_port();
  if (port != ILLEGAL_PORT) {
    DartUtils::PostString(port, line);
  }
}

SSLCertContext* SSLCertContext::GetSecurityContext(Dart_NativeArguments args) {
  SSLCertContext* context;
  Dart_Handle dart_this = ThrowIfError(Dart_GetNativeArgument(args, 0));
  ASSERT(Dart_IsInstance(dart_this));
  ThrowIfError(Dart_GetNativeInstanceField(
      dart_this, SSLCertContext::kSecurityContextNativeFieldIndex,
      reinterpret_cast<intptr_t*>(&context)));
  if (context == nullptr) {
    Dart_PropagateError(Dart_NewUnhandledExceptionError(
        DartUtils::NewInternalError("No native peer")));
  }
  return context;
}

static void DeleteSecurityContext(void* isolate_data, void* context_pointer) {
  SSLCertContext* context = static_cast<SSLCertContext*>(context_pointer);
  context->Release();
}

static Dart_Handle SetSecurityContext(Dart_NativeArguments args,
                                      SSLCertContext* context) {
  Dart_Handle dart_this = Dart_GetNativeArgument(args, 0);
  RETURN_IF_ERROR(dart_this);
  ASSERT(Dart_IsInstance(dart_this));
  Dart_Handle err = Dart_SetNativeInstanceField(
      dart_this, SSLCertContext::kSecurityContextNativeFieldIndex,
      reinterpret_cast<intptr_t>(context));
  RETURN_IF_ERROR(err);
  Dart_NewFinalizableHandle(dart_this, context,
                            SSLCertContext::kApproximateSize,
                            DeleteSecurityContext);
  return Dart_Null();
}

static void ReleaseCertificate(void* isolate_data, void* context_pointer) {
  X509* cert = reinterpret_cast<X509*>(context_pointer);
  X509_free(cert);
}

static intptr_t EstimateX509Size(X509* certificate) {
  intptr_t length = i2d_X509(certificate, nullptr);
  length = length > 0 ? length : 0;
  // An X509 is a tree of structures, which are either opaque or will be opaque
  // in the future. Estimate the overhead to 512 bytes by rounding up
  // sizeof(X509) + sizeof(X509_CINF).
  return length + 512;
}

// Returns the handle for a Dart object wrapping the X509 certificate object.
// The caller should own a reference to the X509 object whose reference count
// won't drop to zero before the ReleaseCertificate finalizer runs.
Dart_Handle X509Helper::WrappedX509Certificate(X509* certificate) {
  if (certificate == nullptr) {
    return Dart_Null();
  }
  Dart_Handle x509_type =
      DartUtils::GetDartType(DartUtils::kIOLibURL, "X509Certificate");
  if (Dart_IsError(x509_type)) {
    X509_free(certificate);
    return x509_type;
  }
  Dart_Handle arguments[] = {nullptr};
  Dart_Handle result =
      Dart_New(x509_type, DartUtils::NewString("_"), 0, arguments);
  if (Dart_IsError(result)) {
    X509_free(certificate);
    return result;
  }
  ASSERT(Dart_IsInstance(result));
  Dart_Handle status =
      Dart_SetNativeInstanceField(result, SSLCertContext::kX509NativeFieldIndex,
                                  reinterpret_cast<intptr_t>(certificate));
  if (Dart_IsError(status)) {
    X509_free(certificate);
    return status;
  }
  const intptr_t approximate_size_of_certificate =
      EstimateX509Size(certificate);
  ASSERT(approximate_size_of_certificate > 0);
  Dart_NewFinalizableHandle(result, reinterpret_cast<void*>(certificate),
                            approximate_size_of_certificate,
                            ReleaseCertificate);
  return result;
}

static int SetTrustedCertificatesBytesPKCS12(SSL_CTX* context,
                                             ScopedMemBIO* bio,
                                             const char* password) {
  CBS cbs;
  CBS_init(&cbs, bio->data(), bio->length());

  EVP_PKEY* key = nullptr;
  ScopedX509Stack cert_stack(sk_X509_new_null());
  int status = PKCS12_get_key_and_certs(&key, cert_stack.get(), &cbs, password);
  if (status == 0) {
    return status;
  }

  X509_STORE* store = SSL_CTX_get_cert_store(context);
  X509* ca;
  while ((ca = sk_X509_shift(cert_stack.get())) != nullptr) {
    status = X509_STORE_add_cert(store, ca);
    // X509_STORE_add_cert increments the reference count of cert on success.
    X509_free(ca);
    if (status == 0) {
      return status;
    }
  }

  return status;
}

static int SetTrustedCertificatesBytesPEM(SSL_CTX* context, BIO* bio) {
  X509_STORE* store = SSL_CTX_get_cert_store(context);

  int status = 0;
  X509* cert = nullptr;
  while ((cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr)) !=
         nullptr) {
    status = X509_STORE_add_cert(store, cert);
    // X509_STORE_add_cert increments the reference count of cert on success.
    X509_free(cert);
    if (status == 0) {
      return status;
    }
  }

  // If no PEM start line is found, it means that we read to the end of the
  // file, or that the file isn't PEM. In the first case, status will be
  // non-zero indicating success. In the second case, status will be 0,
  // indicating that we should try to read as PKCS12. If there is some other
  // error, we return it up to the caller.
  return SecureSocketUtils::NoPEMStartLine() ? status : 0;
}

void SSLCertContext::SetTrustedCertificatesBytes(Dart_Handle cert_bytes,
                                                 const char* password) {
  int status = 0;
  {
    ScopedMemBIO bio(cert_bytes);
    status = SetTrustedCertificatesBytesPEM(context(), bio.bio());
    if (status == 0) {
      if (SecureSocketUtils::NoPEMStartLine()) {
        ERR_clear_error();
        BIO_reset(bio.bio());
        status = SetTrustedCertificatesBytesPKCS12(context(), &bio, password);
      }
    } else {
      // The PEM file was successfully parsed.
      ERR_clear_error();
    }
  }
  SecureSocketUtils::CheckStatus(status, "TlsException",
                                 "Failure trusting builtin roots");
}

static int SetClientAuthoritiesPKCS12(SSL_CTX* context,
                                      ScopedMemBIO* bio,
                                      const char* password) {
  CBS cbs;
  CBS_init(&cbs, bio->data(), bio->length());

  EVP_PKEY* key = nullptr;
  ScopedX509Stack cert_stack(sk_X509_new_null());
  int status = PKCS12_get_key_and_certs(&key, cert_stack.get(), &cbs, password);
  if (status == 0) {
    return status;
  }

  X509* ca;
  while ((ca = sk_X509_shift(cert_stack.get())) != nullptr) {
    status = SSL_CTX_add_client_CA(context, ca);
    // SSL_CTX_add_client_CA increments the reference count of ca on success.
    X509_free(ca);  // The name has been extracted.
    if (status == 0) {
      return status;
    }
  }

  return status;
}

static int SetClientAuthoritiesPEM(SSL_CTX* context, BIO* bio) {
  int status = 0;
  X509* cert = nullptr;
  while ((cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr)) !=
         nullptr) {
    status = SSL_CTX_add_client_CA(context, cert);
    X509_free(cert);  // The name has been extracted.
    if (status == 0) {
      return status;
    }
  }
  return SecureSocketUtils::NoPEMStartLine() ? status : 0;
}

static int SetClientAuthorities(SSL_CTX* context,
                                ScopedMemBIO* bio,
                                const char* password) {
  int status = SetClientAuthoritiesPEM(context, bio->bio());
  if (status == 0) {
    if (SecureSocketUtils::NoPEMStartLine()) {
      ERR_clear_error();
      BIO_reset(bio->bio());
      status = SetClientAuthoritiesPKCS12(context, bio, password);
    }
  } else {
    // The PEM file was successfully parsed.
    ERR_clear_error();
  }
  return status;
}

void SSLCertContext::SetClientAuthoritiesBytes(
    Dart_Handle client_authorities_bytes,
    const char* password) {
  int status;
  {
    ScopedMemBIO bio(client_authorities_bytes);
    status = SetClientAuthorities(context(), &bio, password);
  }

  SecureSocketUtils::CheckStatus(status, "TlsException",
                                 "Failure in setClientAuthoritiesBytes");
}

void SSLCertContext::LoadRootCertFile(const char* file) {
  if (SSL_LOG_STATUS) {
    Syslog::Print("Looking for trusted roots in %s\n", file);
  }
  if (!File::Exists(nullptr, file)) {
    SecureSocketUtils::ThrowIOException(
        -1, "TlsException", "Failed to find root cert file", nullptr);
  }
  int status = SSL_CTX_load_verify_locations(context(), file, nullptr);
  SecureSocketUtils::CheckStatus(status, "TlsException",
                                 "Failure trusting builtin roots");
  if (SSL_LOG_STATUS) {
    Syslog::Print("Trusting roots from: %s\n", file);
  }
}

void SSLCertContext::AddCompiledInCerts() {
  if (root_certificates_pem == nullptr) {
    if (SSL_LOG_STATUS) {
      Syslog::Print("Missing compiled-in roots\n");
    }
    return;
  }
  X509_STORE* store = SSL_CTX_get_cert_store(context());
  BIO* roots_bio =
      BIO_new_mem_buf(const_cast<unsigned char*>(root_certificates_pem),
                      root_certificates_pem_length);
  X509* root_cert;
  // PEM_read_bio_X509 reads PEM-encoded certificates from a bio (in our case,
  // backed by a memory buffer), and returns X509 objects, one by one.
  // When the end of the bio is reached, it returns null.
  while ((root_cert = PEM_read_bio_X509(roots_bio, nullptr, nullptr,
                                        nullptr)) != nullptr) {
    int status = X509_STORE_add_cert(store, root_cert);
    // X509_STORE_add_cert increments the reference count of cert on success.
    X509_free(root_cert);
    if (status == 0) {
      break;
    }
  }
  BIO_free(roots_bio);
  // If there is an error here, it must be the error indicating that we are done
  // reading PEM certificates.
  ASSERT((ERR_peek_error() == 0) || SecureSocketUtils::NoPEMStartLine());
  ERR_clear_error();
}

void SSLCertContext::LoadRootCertCache(const char* cache) {
  if (SSL_LOG_STATUS) {
    Syslog::Print("Looking for trusted roots in %s\n", cache);
  }
  if (Directory::Exists(nullptr, cache) != Directory::EXISTS) {
    SecureSocketUtils::ThrowIOException(
        -1, "TlsException", "Failed to find root cert cache", nullptr);
  }
  int status = SSL_CTX_load_verify_locations(context(), nullptr, cache);
  SecureSocketUtils::CheckStatus(status, "TlsException",
                                 "Failure trusting builtin roots");
  if (SSL_LOG_STATUS) {
    Syslog::Print("Trusting roots from: %s\n", cache);
  }
}

int PasswordCallback(char* buf, int size, int rwflag, void* userdata) {
  char* password = static_cast<char*>(userdata);
  ASSERT(size == PEM_BUFSIZE);
  strncpy(buf, password, size);
  return strlen(password);
}

static EVP_PKEY* GetPrivateKeyPKCS12(BIO* bio, const char* password) {
  ScopedPKCS12 p12(d2i_PKCS12_bio(bio, nullptr));
  if (p12.get() == nullptr) {
    return nullptr;
  }

  EVP_PKEY* key = nullptr;
  X509* cert = nullptr;
  STACK_OF(X509)* ca_certs = nullptr;
  int status = PKCS12_parse(p12.get(), password, &key, &cert, &ca_certs);
  if (status == 0) {
    return nullptr;
  }

  // We only care about the private key.
  ScopedX509 delete_cert(cert);
  ScopedX509Stack delete_ca_certs(ca_certs);
  return key;
}

static EVP_PKEY* GetPrivateKey(BIO* bio, const char* password) {
  EVP_PKEY* key = PEM_read_bio_PrivateKey(bio, nullptr, PasswordCallback,
                                          const_cast<char*>(password));
  if (key == nullptr) {
    // We try reading data as PKCS12 only if reading as PEM was unsuccessful and
    // if there is no indication that the data is malformed PEM. We assume the
    // data is malformed PEM if it contains the start line, i.e. a line
    // with ----- BEGIN.
    if (SecureSocketUtils::NoPEMStartLine()) {
      // Reset the bio, and clear the error from trying to read as PEM.
      ERR_clear_error();
      BIO_reset(bio);

      // Try to decode as PKCS12.
      key = GetPrivateKeyPKCS12(bio, password);
    }
  }
  return key;
}

const char* SSLCertContext::GetPasswordArgument(Dart_NativeArguments args,
                                                intptr_t index) {
  Dart_Handle password_object =
      ThrowIfError(Dart_GetNativeArgument(args, index));
  const char* password = nullptr;
  if (Dart_IsString(password_object)) {
    ThrowIfError(Dart_StringToCString(password_object, &password));
    if (strlen(password) > PEM_BUFSIZE - 1) {
      Dart_ThrowException(DartUtils::NewDartArgumentError(
          "Password length is greater than 1023 (PEM_BUFSIZE)"));
    }
  } else if (Dart_IsNull(password_object)) {
    password = "";
  } else {
    Dart_ThrowException(
        DartUtils::NewDartArgumentError("Password is not a String or null"));
  }
  return password;
}

int AlpnCallback(SSL* ssl,
                 const uint8_t** out,
                 uint8_t* outlen,
                 const uint8_t* in,
                 unsigned int inlen,
                 void* arg) {
  // 'in' and 'arg' are sequences of (length, data) strings with 1-byte lengths.
  // 'arg' is 0-terminated. Finds the first string in 'arg' that is in 'in'.
  uint8_t* server_list = static_cast<uint8_t*>(arg);
  while (*server_list != 0) {
    uint8_t protocol_length = *server_list++;
    const uint8_t* client_list = in;
    while (client_list < in + inlen) {
      uint8_t client_protocol_length = *client_list++;
      if (client_protocol_length == protocol_length) {
        if (0 == memcmp(server_list, client_list, protocol_length)) {
          *out = client_list;
          *outlen = client_protocol_length;
          return SSL_TLSEXT_ERR_OK;  // Success
        }
      }
      client_list += client_protocol_length;
    }
    server_list += protocol_length;
  }
  // TODO(23580): Make failure send a fatal alert instead of ignoring ALPN.
  return SSL_TLSEXT_ERR_NOACK;
}

// Sets the protocol list for ALPN on a SSL object or a context.
void SSLCertContext::SetAlpnProtocolList(Dart_Handle protocols_handle,
                                         SSL* ssl,
                                         SSLCertContext* context,
                                         bool is_server) {
  // Enable ALPN (application layer protocol negotiation) if the caller provides
  // a valid list of supported protocols.
  Dart_TypedData_Type protocols_type;
  uint8_t* protocol_string = nullptr;
  uint8_t* protocol_string_copy = nullptr;
  intptr_t protocol_string_len = 0;
  int status;

  Dart_Handle result = Dart_TypedDataAcquireData(
      protocols_handle, &protocols_type,
      reinterpret_cast<void**>(&protocol_string), &protocol_string_len);
  if (Dart_IsError(result)) {
    Dart_PropagateError(result);
  }

  if (protocols_type != Dart_TypedData_kUint8) {
    Dart_TypedDataReleaseData(protocols_handle);
    Dart_PropagateError(Dart_NewApiError(
        "Unexpected type for protocols (expected valid Uint8List)."));
  }

  if (protocol_string_len > 0) {
    if (is_server) {
      // ALPN on server connections must be set on an SSL_CTX object,
      // not on the SSL object of the individual connection.
      ASSERT(context != nullptr);
      ASSERT(ssl == nullptr);
      // Because it must be passed as a single void*, terminate
      // the list of (length, data) strings with a length 0 string.
      protocol_string_copy =
          static_cast<uint8_t*>(malloc(protocol_string_len + 1));
      memmove(protocol_string_copy, protocol_string, protocol_string_len);
      protocol_string_copy[protocol_string_len] = '\0';
      SSL_CTX_set_alpn_select_cb(context->context(), AlpnCallback,
                                 protocol_string_copy);
      context->set_alpn_protocol_string(protocol_string_copy);
    } else {
      // The function makes a local copy of protocol_string, which it owns.
      if (ssl != nullptr) {
        ASSERT(context == nullptr);
        status = SSL_set_alpn_protos(ssl, protocol_string, protocol_string_len);
      } else {
        ASSERT(context != nullptr);
        ASSERT(ssl == nullptr);
        status = SSL_CTX_set_alpn_protos(context->context(), protocol_string,
                                         protocol_string_len);
      }
      ASSERT(status == 0);  // The function returns a non-standard status.
    }
  }
  Dart_TypedDataReleaseData(protocols_handle);
}

static int UseChainBytesPKCS12(SSL_CTX* context,
                               ScopedMemBIO* bio,
                               const char* password) {
  CBS cbs;
  CBS_init(&cbs, bio->data(), bio->length());

  EVP_PKEY* key = nullptr;
  ScopedX509Stack certs(sk_X509_new_null());
  int status = PKCS12_get_key_and_certs(&key, certs.get(), &cbs, password);
  if (status == 0) {
    return status;
  }

  X509* ca = sk_X509_shift(certs.get());
  status = SSL_CTX_use_certificate(context, ca);
  if (ERR_peek_error() != 0) {
    // Key/certificate mismatch doesn't imply status is 0.
    status = 0;
  }
  X509_free(ca);
  if (status == 0) {
    return status;
  }

  SSL_CTX_clear_chain_certs(context);

  while ((ca = sk_X509_shift(certs.get())) != nullptr) {
    status = SSL_CTX_add0_chain_cert(context, ca);
    // SSL_CTX_add0_chain_cert does not inc ref count, so don't free unless the
    // call fails.
    if (status == 0) {
      X509_free(ca);
      return status;
    }
  }

  return status;
}

static int UseChainBytesPEM(SSL_CTX* context, BIO* bio) {
  int status = 0;
  ScopedX509 x509(PEM_read_bio_X509_AUX(bio, nullptr, nullptr, nullptr));
  if (x509.get() == nullptr) {
    return 0;
  }

  status = SSL_CTX_use_certificate(context, x509.get());
  if (ERR_peek_error() != 0) {
    // Key/certificate mismatch doesn't imply status is 0.
    status = 0;
  }
  if (status == 0) {
    return status;
  }

  SSL_CTX_clear_chain_certs(context);

  X509* ca;
  while ((ca = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr)) != nullptr) {
    status = SSL_CTX_add0_chain_cert(context, ca);
    // SSL_CTX_add0_chain_cert does not inc ref count, so don't free unless the
    // call fails.
    if (status == 0) {
      X509_free(ca);
      return status;
    }
    // Note that we must not free `ca` if it was successfully added to the
    // chain. We must free the main certificate x509, though since its reference
    // count is increased by SSL_CTX_use_certificate.
  }

  return SecureSocketUtils::NoPEMStartLine() ? status : 0;
}

static int UseChainBytes(SSL_CTX* context,
                         ScopedMemBIO* bio,
                         const char* password) {
  int status = UseChainBytesPEM(context, bio->bio());
  if (status == 0) {
    if (SecureSocketUtils::NoPEMStartLine()) {
      ERR_clear_error();
      BIO_reset(bio->bio());
      status = UseChainBytesPKCS12(context, bio, password);
    }
  } else {
    // The PEM file was successfully read.
    ERR_clear_error();
  }
  return status;
}

int SSLCertContext::UseCertificateChainBytes(Dart_Handle cert_chain_bytes,
                                             const char* password) {
  ScopedMemBIO bio(cert_chain_bytes);
  return UseChainBytes(context(), &bio, password);
}

static X509* GetX509Certificate(Dart_NativeArguments args) {
  X509* certificate = nullptr;
  Dart_Handle dart_this = ThrowIfError(Dart_GetNativeArgument(args, 0));
  ASSERT(Dart_IsInstance(dart_this));
  ThrowIfError(Dart_GetNativeInstanceField(
      dart_this, SSLCertContext::kX509NativeFieldIndex,
      reinterpret_cast<intptr_t*>(&certificate)));
  if (certificate == nullptr) {
    Dart_PropagateError(Dart_NewUnhandledExceptionError(
        DartUtils::NewInternalError("No native peer")));
  }
  return certificate;
}

Dart_Handle X509Helper::GetDer(Dart_NativeArguments args) {
  X509* certificate = GetX509Certificate(args);
  // When the second argument is nullptr, i2d_X509() returns the length of the
  // DER encoded cert in bytes.
  intptr_t length = i2d_X509(certificate, nullptr);
  Dart_Handle cert_handle = Dart_NewTypedData(Dart_TypedData_kUint8, length);
  if (Dart_IsError(cert_handle)) {
    Dart_PropagateError(cert_handle);
  }
  Dart_TypedData_Type typ;
  void* dart_cert_bytes = nullptr;
  Dart_Handle status =
      Dart_TypedDataAcquireData(cert_handle, &typ, &dart_cert_bytes, &length);
  if (Dart_IsError(status)) {
    Dart_PropagateError(status);
  }

  // When the second argument points to a non-nullptr buffer address,
  // i2d_X509 fills that buffer with the DER encoded cert data and increments
  // the buffer pointer.
  unsigned char* tmp = static_cast<unsigned char*>(dart_cert_bytes);
  const intptr_t written_length = i2d_X509(certificate, &tmp);
  ASSERT(written_length <= length);
  if (written_length < 0) {
    Dart_TypedDataReleaseData(cert_handle);
    SecureSocketUtils::ThrowIOException(
        -1, "TlsException", "Failed to get certificate bytes", nullptr);
    // SecureSocketUtils::ThrowIOException() does not return.
  }

  status = Dart_TypedDataReleaseData(cert_handle);
  if (Dart_IsError(status)) {
    Dart_PropagateError(status);
  }
  return cert_handle;
}

Dart_Handle X509Helper::GetPem(Dart_NativeArguments args) {
  X509* certificate = GetX509Certificate(args);
  BIO* cert_bio = BIO_new(BIO_s_mem());
  intptr_t status = PEM_write_bio_X509(cert_bio, certificate);
  if (status == 0) {
    BIO_free(cert_bio);
    SecureSocketUtils::ThrowIOException(
        -1, "TlsException", "Failed to write certificate to PEM", nullptr);
    // SecureSocketUtils::ThrowIOException() does not return.
  }

  BUF_MEM* mem = nullptr;
  BIO_get_mem_ptr(cert_bio, &mem);
  Dart_Handle pem_string = Dart_NewStringFromUTF8(
      reinterpret_cast<const uint8_t*>(mem->data), mem->length);
  BIO_free(cert_bio);
  if (Dart_IsError(pem_string)) {
    Dart_PropagateError(pem_string);
  }

  return pem_string;
}

Dart_Handle X509Helper::GetSha1(Dart_NativeArguments args) {
  unsigned char sha1_bytes[EVP_MAX_MD_SIZE];
  X509* certificate = GetX509Certificate(args);
  const EVP_MD* hash_type = EVP_sha1();

  unsigned int sha1_size;
  intptr_t status = X509_digest(certificate, hash_type, sha1_bytes, &sha1_size);
  if (status == 0) {
    SecureSocketUtils::ThrowIOException(
        -1, "TlsException", "Failed to compute certificate's sha1", nullptr);
    // SecureSocketUtils::ThrowIOException() does not return.
  }

  Dart_Handle sha1_handle = Dart_NewTypedData(Dart_TypedData_kUint8, sha1_size);
  if (Dart_IsError(sha1_handle)) {
    Dart_PropagateError(sha1_handle);
  }

  Dart_TypedData_Type typ;
  void* dart_sha1_bytes;
  intptr_t length;
  Dart_Handle result =
      Dart_TypedDataAcquireData(sha1_handle, &typ, &dart_sha1_bytes, &length);
  if (Dart_IsError(result)) {
    Dart_PropagateError(result);
  }

  memmove(dart_sha1_bytes, sha1_bytes, length);

  result = Dart_TypedDataReleaseData(sha1_handle);
  if (Dart_IsError(result)) {
    Dart_PropagateError(result);
  }
  return sha1_handle;
}

Dart_Handle X509Helper::GetSubject(Dart_NativeArguments args) {
  X509* certificate = GetX509Certificate(args);
  X509_NAME* subject = X509_get_subject_name(certificate);
  char* subject_string = X509_NAME_oneline(subject, nullptr, 0);
  if (subject_string == nullptr) {
    Dart_ThrowException(DartUtils::NewDartArgumentError(
        "X509.subject failed to find subject's common name."));
  }
  Dart_Handle subject_handle = Dart_NewStringFromCString(subject_string);
  OPENSSL_free(subject_string);
  return subject_handle;
}

Dart_Handle X509Helper::GetIssuer(Dart_NativeArguments args) {
  X509* certificate = GetX509Certificate(args);
  X509_NAME* issuer = X509_get_issuer_name(certificate);
  char* issuer_string = X509_NAME_oneline(issuer, nullptr, 0);
  if (issuer_string == nullptr) {
    Dart_ThrowException(DartUtils::NewDartArgumentError(
        "X509.issuer failed to find issuer's common name."));
  }
  Dart_Handle issuer_handle = Dart_NewStringFromCString(issuer_string);
  OPENSSL_free(issuer_string);
  return issuer_handle;
}

static Dart_Handle ASN1TimeToMilliseconds(ASN1_TIME* aTime) {
  ASN1_UTCTIME* epoch_start = ASN1_UTCTIME_new();
  ASN1_UTCTIME_set_string(epoch_start, "700101000000Z");
  int days;
  int seconds;
  int result = ASN1_TIME_diff(&days, &seconds, epoch_start, aTime);
  ASN1_UTCTIME_free(epoch_start);
  if (result != 1) {
    // TODO(whesse): Propagate an error to Dart.
    Syslog::PrintErr("ASN1Time error %d\n", result);
  }
  return Dart_NewInteger((86400LL * days + seconds) * 1000LL);
}

Dart_Handle X509Helper::GetStartValidity(Dart_NativeArguments args) {
  X509* certificate = GetX509Certificate(args);
  ASN1_TIME* not_before = X509_get_notBefore(certificate);
  return ASN1TimeToMilliseconds(not_before);
}

Dart_Handle X509Helper::GetEndValidity(Dart_NativeArguments args) {
  X509* certificate = GetX509Certificate(args);
  ASN1_TIME* not_after = X509_get_notAfter(certificate);
  return ASN1TimeToMilliseconds(not_after);
}

void FUNCTION_NAME(SecurityContext_UsePrivateKeyBytes)(
    Dart_NativeArguments args) {
  SSLCertContext* context = SSLCertContext::GetSecurityContext(args);
  const char* password = SSLCertContext::GetPasswordArgument(args, 2);

  int status;
  EVP_PKEY* key;
  {
    ScopedMemBIO bio(ThrowIfError(Dart_GetNativeArgument(args, 1)));
    key = GetPrivateKey(bio.bio(), password);
  }
  if (key == nullptr) {
    Dart_ThrowException(DartUtils::NewDartArgumentError(
        "Expected private key, but none was found"));
  }
  status = SSL_CTX_use_PrivateKey(context->context(), key);
  // SSL_CTX_use_PrivateKey increments the reference count of key on success,
  // so we have to call EVP_PKEY_free on both success and failure.
  EVP_PKEY_free(key);

  // TODO(24184): Handle different expected errors here - file missing,
  // incorrect password, file not a PEM, and throw exceptions.
  // SecureSocketUtils::CheckStatus should also throw an exception in uncaught
  // cases.
  SecureSocketUtils::CheckStatus(status, "TlsException",
                                 "Failure in usePrivateKeyBytes");
}

void FUNCTION_NAME(SecurityContext_Allocate)(Dart_NativeArguments args) {
  SSLFilter::InitializeLibrary();
  SSL_CTX* ctx = SSL_CTX_new(TLS_method());
  SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, SSLCertContext::CertificateCallback);
  SSL_CTX_set_keylog_callback(ctx, SSLCertContext::KeyLogCallback);
  // If we change the minimum protocol version here, then the documentation
  // for `SecurityContext.minimumTlsProtocolVersion` must also be changed.
  SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
  SSL_CTX_set_cipher_list(ctx, "HIGH:MEDIUM");
  SSLCertContext* context = new SSLCertContext(ctx);
  Dart_Handle err = SetSecurityContext(args, context);
  if (Dart_IsError(err)) {
    delete context;
    Dart_PropagateError(err);
  }
}

void FUNCTION_NAME(SecurityContext_SetTrustedCertificatesBytes)(
    Dart_NativeArguments args) {
  SSLCertContext* context = SSLCertContext::GetSecurityContext(args);
  Dart_Handle cert_bytes = ThrowIfError(Dart_GetNativeArgument(args, 1));
  const char* password = SSLCertContext::GetPasswordArgument(args, 2);

  ASSERT(context != nullptr);
  ASSERT(password != nullptr);
  context->SetTrustedCertificatesBytes(cert_bytes, password);
}

void FUNCTION_NAME(SecurityContext_SetClientAuthoritiesBytes)(
    Dart_NativeArguments args) {
  SSLCertContext* context = SSLCertContext::GetSecurityContext(args);
  Dart_Handle client_authorities_bytes =
      ThrowIfError(Dart_GetNativeArgument(args, 1));
  const char* password = SSLCertContext::GetPasswordArgument(args, 2);

  ASSERT(context != nullptr);
  ASSERT(password != nullptr);

  context->SetClientAuthoritiesBytes(client_authorities_bytes, password);
}

void FUNCTION_NAME(SecurityContext_UseCertificateChainBytes)(
    Dart_NativeArguments args) {
  SSLCertContext* context = SSLCertContext::GetSecurityContext(args);
  Dart_Handle cert_chain_bytes = ThrowIfError(Dart_GetNativeArgument(args, 1));
  const char* password = SSLCertContext::GetPasswordArgument(args, 2);

  ASSERT(context != nullptr);
  ASSERT(password != nullptr);

  int status = context->UseCertificateChainBytes(cert_chain_bytes, password);

  SecureSocketUtils::CheckStatus(status, "TlsException",
                                 "Failure in useCertificateChainBytes");
}

void FUNCTION_NAME(SecurityContext_TrustBuiltinRoots)(
    Dart_NativeArguments args) {
  SSLCertContext* context = SSLCertContext::GetSecurityContext(args);

  ASSERT(context != nullptr);

  context->TrustBuiltinRoots();
}

void FUNCTION_NAME(SecurityContext_SetAllowTlsRenegotiation)(
    Dart_NativeArguments args) {
  SSLCertContext* context = SSLCertContext::GetSecurityContext(args);
  Dart_Handle allow_tls_handle = ThrowIfError(Dart_GetNativeArgument(args, 1));

  ASSERT(context != nullptr);
  ASSERT(allow_tls_handle != nullptr);

  if (!Dart_IsBoolean(allow_tls_handle)) {
    Dart_ThrowException(DartUtils::NewDartArgumentError(
        "Non-boolean argument passed to SetAllowTlsRenegotiation"));
  }
  bool allow = DartUtils::GetBooleanValue(allow_tls_handle);
  context->set_allow_tls_renegotiation(allow);
}

void FUNCTION_NAME(SecurityContext_SetMinimumProtocolVersion)(
    Dart_NativeArguments args) {
  SSLCertContext* context = SSLCertContext::GetSecurityContext(args);
  Dart_Handle protocol_version_handle =
      ThrowIfError(Dart_GetNativeArgument(args, 1));
  if (!Dart_IsInteger(protocol_version_handle)) {
    Dart_ThrowException(DartUtils::NewDartArgumentError(
        "Non-int argument passed to SetMinimumProtocolVersion"));
  }

  int protocol_version = DartUtils::GetIntegerValue(protocol_version_handle);
  if (SSL_CTX_set_min_proto_version(context->context(), protocol_version) ==
      0) {
    Dart_ThrowException(DartUtils::NewDartArgumentError(
        "Invalid protocol version passed to SetMinimumProtocolVersion"));
  }
}

void FUNCTION_NAME(SecurityContext_GetMinimumProtocolVersion)(
    Dart_NativeArguments args) {
  SSLCertContext* context = SSLCertContext::GetSecurityContext(args);

  Dart_SetIntegerReturnValue(args,
                             SSL_CTX_get_min_proto_version(context->context()));
}

void FUNCTION_NAME(X509_Der)(Dart_NativeArguments args) {
  Dart_SetReturnValue(args, X509Helper::GetDer(args));
}

void FUNCTION_NAME(X509_Pem)(Dart_NativeArguments args) {
  Dart_SetReturnValue(args, X509Helper::GetPem(args));
}

void FUNCTION_NAME(X509_Sha1)(Dart_NativeArguments args) {
  Dart_SetReturnValue(args, X509Helper::GetSha1(args));
}

void FUNCTION_NAME(X509_Subject)(Dart_NativeArguments args) {
  Dart_SetReturnValue(args, X509Helper::GetSubject(args));
}

void FUNCTION_NAME(X509_Issuer)(Dart_NativeArguments args) {
  Dart_SetReturnValue(args, X509Helper::GetIssuer(args));
}

void FUNCTION_NAME(X509_StartValidity)(Dart_NativeArguments args) {
  Dart_SetReturnValue(args, X509Helper::GetStartValidity(args));
}

void FUNCTION_NAME(X509_EndValidity)(Dart_NativeArguments args) {
  Dart_SetReturnValue(args, X509Helper::GetEndValidity(args));
}

void FUNCTION_NAME(SecurityContext_SetAlpnProtocols)(
    Dart_NativeArguments args) {
  SSLCertContext* context = SSLCertContext::GetSecurityContext(args);
  Dart_Handle protocols_handle = ThrowIfError(Dart_GetNativeArgument(args, 1));
  Dart_Handle is_server_handle = ThrowIfError(Dart_GetNativeArgument(args, 2));
  if (Dart_IsBoolean(is_server_handle)) {
    bool is_server = DartUtils::GetBooleanValue(is_server_handle);
    SSLCertContext::SetAlpnProtocolList(protocols_handle, nullptr, context,
                                        is_server);
  } else {
    Dart_ThrowException(DartUtils::NewDartArgumentError(
        "Non-boolean is_server argument passed to SetAlpnProtocols"));
  }
}

}  // namespace bin
}  // namespace dart

#endif  // !defined(DART_IO_SECURE_SOCKET_DISABLED)
