//
//
// Copyright 2020 gRPC authors.
//
// 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
//
//     http://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 "src/core/credentials/transport/xds/xds_credentials.h"

#include <grpc/grpc_security_constants.h>
#include <grpc/impl/channel_arg_names.h>
#include <grpc/support/port_platform.h>

#include <optional>

#include "src/core/credentials/transport/tls/grpc_tls_certificate_provider.h"
#include "src/core/credentials/transport/tls/grpc_tls_credentials_options.h"
#include "src/core/credentials/transport/tls/tls_credentials.h"
#include "src/core/credentials/transport/tls/tls_utils.h"
#include "src/core/lib/channel/channel_args.h"
#include "src/core/load_balancing/xds/xds_channel_args.h"
#include "src/core/util/env.h"
#include "src/core/util/grpc_check.h"
#include "src/core/util/host_port.h"
#include "src/core/util/useful.h"
#include "src/core/xds/grpc/xds_certificate_provider.h"

namespace grpc_core {

namespace {

// TODO(mlumish): Remove this after 1.80
bool UseChannelAuthorityIfNoSNIApplicable() {
  auto value = GetEnv("GRPC_USE_CHANNEL_AUTHORITY_IF_NO_SNI_APPLICABLE");
  if (!value.has_value()) return false;
  bool parsed_value;
  bool parse_succeeded = gpr_parse_bool_value(value->c_str(), &parsed_value);
  return parse_succeeded && parsed_value;
}

bool XdsVerifySubjectAlternativeNames(
    const char* const* subject_alternative_names,
    size_t subject_alternative_names_size,
    const std::vector<StringMatcher>& matchers) {
  if (matchers.empty()) return true;
  for (size_t i = 0; i < subject_alternative_names_size; ++i) {
    for (const auto& matcher : matchers) {
      if (matcher.type() == StringMatcher::Type::kExact) {
        // For Exact match, use DNS rules for verifying SANs
        // TODO(zhenlian): Right now, the SSL layer does not save the type of
        // the SAN, so we are doing a DNS style verification for all SANs when
        // the type is EXACT. When we expose the SAN type, change this to only
        // do this verification when the SAN type is DNS and match type is
        // kExact. For all other cases, we should use matcher.Match().
        if (VerifySubjectAlternativeName(subject_alternative_names[i],
                                         matcher.string_matcher())) {
          return true;
        }
      } else {
        if (matcher.Match(subject_alternative_names[i])) {
          return true;
        }
      }
    }
  }
  return false;
}

}  // namespace

//
// XdsCertificateVerifier
//

XdsCertificateVerifier::XdsCertificateVerifier(
    RefCountedPtr<XdsCertificateProvider> xds_certificate_provider,
    absl::string_view sni_name)
    : xds_certificate_provider_(std::move(xds_certificate_provider)),
      sni_name_(sni_name) {}

bool XdsCertificateVerifier::Verify(
    grpc_tls_custom_verification_check_request* request,
    std::function<void(absl::Status)>, absl::Status* sync_status) {
  GRPC_CHECK_NE(request, nullptr);
  if (xds_certificate_provider_->auto_sni_san_validation()) {
    if (!XdsVerifySubjectAlternativeNames(
            request->peer_info.san_names.dns_names,
            request->peer_info.san_names.dns_names_size,
            {StringMatcher::Create(StringMatcher::Type::kExact, sni_name_, true)
                 .value()})) {
      *sync_status = absl::UnauthenticatedError(
          "SANs from certificate did not match SNI from xDS control plane");
    }
  } else {
    if (!XdsVerifySubjectAlternativeNames(
            request->peer_info.san_names.uri_names,
            request->peer_info.san_names.uri_names_size,
            xds_certificate_provider_->san_matchers()) &&
        !XdsVerifySubjectAlternativeNames(
            request->peer_info.san_names.ip_names,
            request->peer_info.san_names.ip_names_size,
            xds_certificate_provider_->san_matchers()) &&
        !XdsVerifySubjectAlternativeNames(
            request->peer_info.san_names.dns_names,
            request->peer_info.san_names.dns_names_size,
            xds_certificate_provider_->san_matchers())) {
      *sync_status = absl::UnauthenticatedError(
          "SANs from certificate did not match SANs from xDS control plane");
    }
  }
  return true;  // synchronous check
}

void XdsCertificateVerifier::Cancel(
    grpc_tls_custom_verification_check_request*) {}

int XdsCertificateVerifier::CompareImpl(
    const grpc_tls_certificate_verifier* other) const {
  auto* o = static_cast<const XdsCertificateVerifier*>(other);
  int compare_cert_provider;
  if (xds_certificate_provider_ == nullptr ||
      o->xds_certificate_provider_ == nullptr) {
    compare_cert_provider =
        QsortCompare(xds_certificate_provider_, o->xds_certificate_provider_);
  } else {
    compare_cert_provider =
        xds_certificate_provider_->Compare(o->xds_certificate_provider_.get());
  }
  if (compare_cert_provider != 0) {
    return compare_cert_provider;
  }
  return sni_name_.compare(o->sni_name_);
}

UniqueTypeName XdsCertificateVerifier::type() const {
  static UniqueTypeName::Factory kFactory("Xds");
  return kFactory.Create();
}

bool TestOnlyXdsVerifySubjectAlternativeNames(
    const char* const* subject_alternative_names,
    size_t subject_alternative_names_size,
    const std::vector<StringMatcher>& matchers) {
  return XdsVerifySubjectAlternativeNames(
      subject_alternative_names, subject_alternative_names_size, matchers);
}

//
// XdsCredentials
//

RefCountedPtr<grpc_channel_security_connector>
XdsCredentials::create_security_connector(
    RefCountedPtr<grpc_call_credentials> call_creds, const char* target_name,
    ChannelArgs* args) {
  RefCountedPtr<grpc_channel_security_connector> security_connector;
  auto xds_certificate_provider = args->GetObjectRef<XdsCertificateProvider>();
  if (xds_certificate_provider != nullptr) {
    const bool watch_root = xds_certificate_provider->ProvidesRootCerts();
    const bool use_system_root_certs =
        xds_certificate_provider->UseSystemRootCerts();
    const bool watch_identity =
        xds_certificate_provider->ProvidesIdentityCerts();
    if (watch_root || use_system_root_certs || watch_identity) {
      auto tls_credentials_options =
          MakeRefCounted<grpc_tls_credentials_options>();
      if (watch_root) {
        tls_credentials_options->set_root_certificate_provider(
            xds_certificate_provider);
      }
      if (watch_identity) {
        tls_credentials_options->set_identity_certificate_provider(
            xds_certificate_provider);
      }
      tls_credentials_options->set_verify_server_cert(true);
      auto hostname = args->GetOwnedString(GRPC_ARG_ADDRESS_NAME);
      if (xds_certificate_provider->auto_host_sni() && hostname.has_value()) {
        std::string host;
        std::string port;
        SplitHostPort(*hostname, &host, &port);
        tls_credentials_options->set_sni_override(host);
      } else if (!xds_certificate_provider->sni().empty()) {
        tls_credentials_options->set_sni_override(
            xds_certificate_provider->sni());
      } else {
        if (!UseChannelAuthorityIfNoSNIApplicable()) {
          tls_credentials_options->set_sni_override("");
        }
      }
      tls_credentials_options->set_certificate_verifier(
          MakeRefCounted<XdsCertificateVerifier>(
              std::move(xds_certificate_provider),
              tls_credentials_options->sni_override().value_or("")));
      tls_credentials_options->set_check_call_host(false);
      auto tls_credentials =
          MakeRefCounted<TlsCredentials>(std::move(tls_credentials_options));
      return tls_credentials->create_security_connector(std::move(call_creds),
                                                        target_name, args);
    }
  }
  GRPC_CHECK(fallback_credentials_ != nullptr);
  return fallback_credentials_->create_security_connector(std::move(call_creds),
                                                          target_name, args);
}

UniqueTypeName XdsCredentials::Type() {
  static UniqueTypeName::Factory kFactory("Xds");
  return kFactory.Create();
}

//
// XdsServerCredentials
//

RefCountedPtr<grpc_server_security_connector>
XdsServerCredentials::create_security_connector(const ChannelArgs& args) {
  auto xds_certificate_provider = args.GetObjectRef<XdsCertificateProvider>();
  // Identity certs are a must for TLS.
  if (xds_certificate_provider != nullptr &&
      xds_certificate_provider->ProvidesIdentityCerts()) {
    auto tls_credentials_options =
        MakeRefCounted<grpc_tls_credentials_options>();
    tls_credentials_options->set_identity_certificate_provider(
        xds_certificate_provider);
    if (xds_certificate_provider->ProvidesRootCerts()) {
      tls_credentials_options->set_root_certificate_provider(
          xds_certificate_provider);
      tls_credentials_options->set_cert_request_type(
          xds_certificate_provider->require_client_certificate()
              ? GRPC_SSL_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY
              : GRPC_SSL_REQUEST_CLIENT_CERTIFICATE_AND_VERIFY);
    } else {
      // Do not request client certificate if there is no way to verify.
      tls_credentials_options->set_cert_request_type(
          GRPC_SSL_DONT_REQUEST_CLIENT_CERTIFICATE);
    }
    auto tls_credentials = MakeRefCounted<TlsServerCredentials>(
        std::move(tls_credentials_options));
    return tls_credentials->create_security_connector(args);
  }
  return fallback_credentials_->create_security_connector(args);
}

UniqueTypeName XdsServerCredentials::Type() {
  static UniqueTypeName::Factory kFactory("Xds");
  return kFactory.Create();
}

}  // namespace grpc_core

grpc_channel_credentials* grpc_xds_credentials_create(
    grpc_channel_credentials* fallback_credentials) {
  GRPC_CHECK_NE(fallback_credentials, nullptr);
  return new grpc_core::XdsCredentials(fallback_credentials->Ref());
}

grpc_server_credentials* grpc_xds_server_credentials_create(
    grpc_server_credentials* fallback_credentials) {
  GRPC_CHECK_NE(fallback_credentials, nullptr);
  return new grpc_core::XdsServerCredentials(fallback_credentials->Ref());
}
