// Copyright 1995-2016 The OpenSSL Project Authors. All Rights Reserved.
//
// 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
//
//     https://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 <openssl/ssl.h>

#include <string.h>

#include <openssl/asn1.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/mem.h>
#include <openssl/pem.h>
#include <openssl/stack.h>
#include <openssl/x509.h>

#include "internal.h"


using namespace bssl;

static int xname_cmp(const X509_NAME *const *a, const X509_NAME *const *b) {
  return X509_NAME_cmp(*a, *b);
}

static int add_bio_cert_subjects_to_stack(STACK_OF(X509_NAME) *out, BIO *bio,
                                          bool allow_empty) {
  // This function historically sorted `out` after every addition and skipped
  // duplicates. This implementation preserves that behavior, but only sorts at
  // the end, to avoid a quadratic running time. Existing duplicates in `out`
  // are preserved, but do not introduce new duplicates.
  UniquePtr<STACK_OF(X509_NAME)> to_append(sk_X509_NAME_new(xname_cmp));
  if (to_append == nullptr) {
    return 0;
  }

  // Temporarily switch the comparison function for `out`.
  struct RestoreCmpFunc {
    ~RestoreCmpFunc() { sk_X509_NAME_set_cmp_func(stack, old_cmp); }
    STACK_OF(X509_NAME) *stack;
    int (*old_cmp)(const X509_NAME *const *, const X509_NAME *const *);
  };
  RestoreCmpFunc restore = {out, sk_X509_NAME_set_cmp_func(out, xname_cmp)};

  sk_X509_NAME_sort(out);
  bool first = true;
  for (;;) {
    UniquePtr<X509> x509(PEM_read_bio_X509(bio, nullptr, nullptr, nullptr));
    if (x509 == nullptr) {
      if (first && !allow_empty) {
        return 0;
      }
      // TODO(davidben): This ignores PEM syntax errors. It should only succeed
      // on `PEM_R_NO_START_LINE`.
      ERR_clear_error();
      break;
    }
    first = false;

    X509_NAME *subject = X509_get_subject_name(x509.get());
    // Skip if already present in `out`. Duplicates in `to_append` will be
    // handled separately.
    if (sk_X509_NAME_find(out, /*out_index=*/nullptr, subject)) {
      continue;
    }

    UniquePtr<X509_NAME> copy(X509_NAME_dup(subject));
    if (copy == nullptr || !PushToStack(to_append.get(), std::move(copy))) {
      return 0;
    }
  }

  // Append `to_append` to `stack`, skipping any duplicates.
  sk_X509_NAME_sort(to_append.get());
  size_t num = sk_X509_NAME_num(to_append.get());
  for (size_t i = 0; i < num; i++) {
    UniquePtr<X509_NAME> name(sk_X509_NAME_value(to_append.get(), i));
    sk_X509_NAME_set(to_append.get(), i, nullptr);
    if (i + 1 < num &&
        X509_NAME_cmp(name.get(), sk_X509_NAME_value(to_append.get(), i + 1)) ==
            0) {
      continue;
    }
    if (!PushToStack(out, std::move(name))) {
      return 0;
    }
  }

  // Sort `out` one last time, to preserve the historical behavior of
  // maintaining the sorted list.
  sk_X509_NAME_sort(out);
  return 1;
}

int SSL_add_bio_cert_subjects_to_stack(STACK_OF(X509_NAME) *out, BIO *bio) {
  return add_bio_cert_subjects_to_stack(out, bio, /*allow_empty=*/true);
}

STACK_OF(X509_NAME) *SSL_load_client_CA_file(const char *file) {
  UniquePtr<BIO> in(BIO_new_file(file, "rb"));
  if (in == nullptr) {
    return nullptr;
  }
  UniquePtr<STACK_OF(X509_NAME)> ret(sk_X509_NAME_new_null());
  if (ret == nullptr ||  //
      !add_bio_cert_subjects_to_stack(ret.get(), in.get(),
                                      /*allow_empty=*/false)) {
    return nullptr;
  }
  return ret.release();
}

int SSL_add_file_cert_subjects_to_stack(STACK_OF(X509_NAME) *out,
                                        const char *file) {
  UniquePtr<BIO> in(BIO_new_file(file, "rb"));
  if (in == nullptr) {
    return 0;
  }
  return SSL_add_bio_cert_subjects_to_stack(out, in.get());
}

int SSL_use_certificate_file(SSL *ssl, const char *file, int type) {
  SSLContext *ctx = FromOpaque(SSL_get_SSL_CTX(ssl));
  UniquePtr<BIO> in(BIO_new_file(file, "rb"));
  if (in == nullptr) {
    OPENSSL_PUT_ERROR(SSL, ERR_R_BUF_LIB);
    return 0;
  }

  int reason_code;
  UniquePtr<X509> x;
  if (type == SSL_FILETYPE_ASN1) {
    reason_code = ERR_R_ASN1_LIB;
    x.reset(d2i_X509_bio(in.get(), nullptr));
  } else if (type == SSL_FILETYPE_PEM) {
    reason_code = ERR_R_PEM_LIB;
    x.reset(PEM_read_bio_X509(in.get(), nullptr, ctx->default_passwd_callback,
                              ctx->default_passwd_callback_userdata));
  } else {
    OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_SSL_FILETYPE);
    return 0;
  }

  if (x == nullptr) {
    OPENSSL_PUT_ERROR(SSL, reason_code);
    return 0;
  }

  return SSL_use_certificate(ssl, x.get());
}

int SSL_use_RSAPrivateKey_file(SSL *ssl, const char *file, int type) {
  SSLContext *ctx = FromOpaque(SSL_get_SSL_CTX(ssl));
  UniquePtr<BIO> in(BIO_new_file(file, "rb"));
  if (in == nullptr) {
    OPENSSL_PUT_ERROR(SSL, ERR_R_BUF_LIB);
    return 0;
  }

  int reason_code;
  UniquePtr<RSA> rsa;
  if (type == SSL_FILETYPE_ASN1) {
    reason_code = ERR_R_ASN1_LIB;
    rsa.reset(d2i_RSAPrivateKey_bio(in.get(), nullptr));
  } else if (type == SSL_FILETYPE_PEM) {
    reason_code = ERR_R_PEM_LIB;
    rsa.reset(PEM_read_bio_RSAPrivateKey(
        in.get(), nullptr, ctx->default_passwd_callback,
        ctx->default_passwd_callback_userdata));
  } else {
    OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_SSL_FILETYPE);
    return 0;
  }

  if (rsa == nullptr) {
    OPENSSL_PUT_ERROR(SSL, reason_code);
    return 0;
  }
  return SSL_use_RSAPrivateKey(ssl, rsa.get());
}

int SSL_use_PrivateKey_file(SSL *ssl, const char *file, int type) {
  SSLContext *ctx = FromOpaque(SSL_get_SSL_CTX(ssl));
  UniquePtr<BIO> in(BIO_new_file(file, "rb"));
  if (in == nullptr) {
    OPENSSL_PUT_ERROR(SSL, ERR_R_BUF_LIB);
    return 0;
  }

  int reason_code;
  UniquePtr<EVP_PKEY> pkey;
  if (type == SSL_FILETYPE_PEM) {
    reason_code = ERR_R_PEM_LIB;
    pkey.reset(PEM_read_bio_PrivateKey(in.get(), nullptr,
                                       ctx->default_passwd_callback,
                                       ctx->default_passwd_callback_userdata));
  } else if (type == SSL_FILETYPE_ASN1) {
    reason_code = ERR_R_ASN1_LIB;
    pkey.reset(d2i_PrivateKey_bio(in.get(), nullptr));
  } else {
    OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_SSL_FILETYPE);
    return 0;
  }

  if (pkey == nullptr) {
    OPENSSL_PUT_ERROR(SSL, reason_code);
    return 0;
  }

  return SSL_use_PrivateKey(ssl, pkey.get());
}

int SSL_CTX_use_certificate_file(SSL_CTX *ctx, const char *file, int type) {
  auto *ctx_impl = FromOpaque(ctx);
  UniquePtr<BIO> in(BIO_new_file(file, "rb"));
  if (in == nullptr) {
    OPENSSL_PUT_ERROR(SSL, ERR_R_BUF_LIB);
    return 0;
  }

  int reason_code;
  UniquePtr<X509> x;
  if (type == SSL_FILETYPE_ASN1) {
    reason_code = ERR_R_ASN1_LIB;
    x.reset(d2i_X509_bio(in.get(), nullptr));
  } else if (type == SSL_FILETYPE_PEM) {
    reason_code = ERR_R_PEM_LIB;
    x.reset(PEM_read_bio_X509(in.get(), nullptr,
                              ctx_impl->default_passwd_callback,
                              ctx_impl->default_passwd_callback_userdata));
  } else {
    OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_SSL_FILETYPE);
    return 0;
  }

  if (x == nullptr) {
    OPENSSL_PUT_ERROR(SSL, reason_code);
    return 0;
  }

  return SSL_CTX_use_certificate(ctx, x.get());
}

int SSL_CTX_use_RSAPrivateKey_file(SSL_CTX *ctx, const char *file, int type) {
  auto *ctx_impl = FromOpaque(ctx);
  UniquePtr<BIO> in(BIO_new_file(file, "rb"));
  if (in == nullptr) {
    OPENSSL_PUT_ERROR(SSL, ERR_R_BUF_LIB);
    return 0;
  }

  int reason_code;
  UniquePtr<RSA> rsa;
  if (type == SSL_FILETYPE_ASN1) {
    reason_code = ERR_R_ASN1_LIB;
    rsa.reset(d2i_RSAPrivateKey_bio(in.get(), nullptr));
  } else if (type == SSL_FILETYPE_PEM) {
    reason_code = ERR_R_PEM_LIB;
    rsa.reset(PEM_read_bio_RSAPrivateKey(
        in.get(), nullptr, ctx_impl->default_passwd_callback,
        ctx_impl->default_passwd_callback_userdata));
  } else {
    OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_SSL_FILETYPE);
    return 0;
  }

  if (rsa == nullptr) {
    OPENSSL_PUT_ERROR(SSL, reason_code);
    return 0;
  }
  return SSL_CTX_use_RSAPrivateKey(ctx, rsa.get());
}

int SSL_CTX_use_PrivateKey_file(SSL_CTX *ctx, const char *file, int type) {
  auto *ctx_impl = FromOpaque(ctx);
  UniquePtr<BIO> in(BIO_new_file(file, "rb"));
  if (in == nullptr) {
    OPENSSL_PUT_ERROR(SSL, ERR_R_BUF_LIB);
    return 0;
  }

  int reason_code;
  UniquePtr<EVP_PKEY> pkey;
  if (type == SSL_FILETYPE_PEM) {
    reason_code = ERR_R_PEM_LIB;
    pkey.reset(PEM_read_bio_PrivateKey(
        in.get(), nullptr, ctx_impl->default_passwd_callback,
        ctx_impl->default_passwd_callback_userdata));
  } else if (type == SSL_FILETYPE_ASN1) {
    reason_code = ERR_R_ASN1_LIB;
    pkey.reset(d2i_PrivateKey_bio(in.get(), nullptr));
  } else {
    OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_SSL_FILETYPE);
    return 0;
  }

  if (pkey == nullptr) {
    OPENSSL_PUT_ERROR(SSL, reason_code);
    return 0;
  }

  return SSL_CTX_use_PrivateKey(ctx, pkey.get());
}

// Read a file that contains our certificate in "PEM" format, possibly followed
// by a sequence of CA certificates that should be sent to the peer in the
// Certificate message.
int SSL_CTX_use_certificate_chain_file(SSL_CTX *ctx, const char *file) {
  auto *ctx_impl = FromOpaque(ctx);
  UniquePtr<BIO> in(BIO_new_file(file, "rb"));
  if (in == nullptr) {
    OPENSSL_PUT_ERROR(SSL, ERR_R_BUF_LIB);
    return 0;
  }

  UniquePtr<X509> x(PEM_read_bio_X509_AUX(
      in.get(), nullptr, ctx_impl->default_passwd_callback,
      ctx_impl->default_passwd_callback_userdata));
  if (x == nullptr) {
    OPENSSL_PUT_ERROR(SSL, ERR_R_PEM_LIB);
    return 0;
  }

  if (!SSL_CTX_use_certificate(ctx, x.get())) {
    return 0;
  }

  // If we could set up our certificate, now proceed to the CA certificates.
  SSL_CTX_clear_chain_certs(ctx);
  for (;;) {
    UniquePtr<X509> ca(
        PEM_read_bio_X509(in.get(), nullptr, ctx_impl->default_passwd_callback,
                          ctx_impl->default_passwd_callback_userdata));
    if (ca == nullptr) {
      break;
    }
    if (!SSL_CTX_add1_chain_cert(ctx, ca.get())) {
      return 0;
    }
  }

  // When the while loop ends, it's usually just EOF.
  if (ERR_equals(ERR_peek_last_error(), ERR_LIB_PEM, PEM_R_NO_START_LINE)) {
    ERR_clear_error();
    return 1;
  }

  return 0;  // Some real error.
}

void SSL_CTX_set_default_passwd_cb(SSL_CTX *ctx, pem_password_cb *cb) {
  FromOpaque(ctx)->default_passwd_callback = cb;
}

pem_password_cb *SSL_CTX_get_default_passwd_cb(const SSL_CTX *ctx) {
  return FromOpaque(ctx)->default_passwd_callback;
}

void SSL_CTX_set_default_passwd_cb_userdata(SSL_CTX *ctx, void *data) {
  FromOpaque(ctx)->default_passwd_callback_userdata = data;
}

void *SSL_CTX_get_default_passwd_cb_userdata(const SSL_CTX *ctx) {
  return FromOpaque(ctx)->default_passwd_callback_userdata;
}
