chromium/third_party/grpc/src/src/core/tsi/ssl_transport_security.cc

//
//
// Copyright 2015 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 <grpc/support/port_platform.h>

#include "src/core/tsi/ssl_transport_security.h"

#include <limits.h>
#include <string.h>

// TODO(jboeuf): refactor inet_ntop into a portability header.
// Note: for whomever reads this and tries to refactor this, this
// can't be in grpc, it has to be in gpr.
#ifdef GPR_WINDOWS
#include <ws2tcpip.h>
#else
#include <arpa/inet.h>
#include <sys/socket.h>
#endif

#include <string>

#include <openssl/bio.h>
#include <openssl/crypto.h>  // For OPENSSL_free
#include <openssl/engine.h>
#include <openssl/err.h>
#include <openssl/ssl.h>
#include <openssl/tls1.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>

#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"

#include <grpc/grpc_security.h>
#include <grpc/support/alloc.h>
#include <grpc/support/log.h>
#include <grpc/support/string_util.h>
#include <grpc/support/sync.h>
#include <grpc/support/thd_id.h>

#include "src/core/lib/gpr/useful.h"
#include "src/core/lib/gprpp/crash.h"
#include "src/core/tsi/ssl/key_logging/ssl_key_logging.h"
#include "src/core/tsi/ssl/session_cache/ssl_session_cache.h"
#include "src/core/tsi/ssl_transport_security_utils.h"
#include "src/core/tsi/ssl_types.h"
#include "src/core/tsi/transport_security.h"

// --- Constants. ---

#define TSI_SSL_MAX_PROTECTED_FRAME_SIZE_UPPER_BOUND
#define TSI_SSL_MAX_PROTECTED_FRAME_SIZE_LOWER_BOUND
#define TSI_SSL_HANDSHAKER_OUTGOING_BUFFER_INITIAL_SIZE

// Putting a macro like this and littering the source file with #if is really
// bad practice.
// TODO(jboeuf): refactor all the #if / #endif in a separate module.
#ifndef TSI_OPENSSL_ALPN_SUPPORT
#define TSI_OPENSSL_ALPN_SUPPORT
#endif

// TODO(jboeuf): I have not found a way to get this number dynamically from the
// SSL structure. This is what we would ultimately want though...
#define TSI_SSL_MAX_PROTECTION_OVERHEAD

TlsSessionKeyLogger;

// --- Structure definitions. ---

struct tsi_ssl_root_certs_store {};

struct tsi_ssl_handshaker_factory {};

struct tsi_ssl_client_handshaker_factory {};

struct tsi_ssl_server_handshaker_factory {};

struct tsi_ssl_handshaker {};
struct tsi_ssl_handshaker_result {};
struct tsi_ssl_frame_protector {};
// --- Library Initialization. ---

static gpr_once g_init_openssl_once =;
static int g_ssl_ctx_ex_factory_index =;
static const unsigned char kSslSessionIdContext[] =;
static int g_ssl_ex_verified_root_cert_index =;
#if !defined(OPENSSL_IS_BORINGSSL) && !defined(OPENSSL_NO_ENGINE)
static const char kSslEnginePrefix[] = "engine:";
#endif

#if OPENSSL_VERSION_NUMBER < 0x10100000
static gpr_mu* g_openssl_mutexes = nullptr;
static void openssl_locking_cb(int mode, int type, const char* file,
                               int line) GRPC_UNUSED;
static unsigned long openssl_thread_id_cb(void) GRPC_UNUSED;

static void openssl_locking_cb(int mode, int type, const char* file, int line) {
  if (mode & CRYPTO_LOCK) {
    gpr_mu_lock(&g_openssl_mutexes[type]);
  } else {
    gpr_mu_unlock(&g_openssl_mutexes[type]);
  }
}

static unsigned long openssl_thread_id_cb(void) {
  return static_cast<unsigned long>(gpr_thd_currentid());
}
#endif

static void init_openssl(void) {}

// --- Ssl utils. ---

// TODO(jboeuf): Remove when we are past the debugging phase with this code.
static void ssl_log_where_info(const SSL* ssl, int where, int flag,
                               const char* msg) {}

// Used for debugging. TODO(jboeuf): Remove when code is mature enough.
static void ssl_info_callback(const SSL* ssl, int where, int ret) {}

// Returns 1 if name looks like an IP address, 0 otherwise.
// This is a very rough heuristic, and only handles IPv6 in hexadecimal form.
static int looks_like_ip_address(absl::string_view name) {}

// Gets the subject CN from an X509 cert.
static tsi_result ssl_get_x509_common_name(X509* cert, unsigned char** utf8,
                                           size_t* utf8_size) {}

// Gets the subject CN of an X509 cert as a tsi_peer_property.
static tsi_result peer_property_from_x509_common_name(
    X509* cert, tsi_peer_property* property) {}

// Gets the subject of an X509 cert as a tsi_peer_property.
static tsi_result peer_property_from_x509_subject(X509* cert,
                                                  tsi_peer_property* property,
                                                  bool is_verified_root_cert) {}

// Gets the X509 cert in PEM format as a tsi_peer_property.
static tsi_result add_pem_certificate(X509* cert, tsi_peer_property* property) {}

// Gets the subject SANs from an X509 cert as a tsi_peer_property.
static tsi_result add_subject_alt_names_properties_to_peer(
    tsi_peer* peer, GENERAL_NAMES* subject_alt_names,
    size_t subject_alt_name_count, int* current_insert_index) {}

// Gets information about the peer's X509 cert as a tsi_peer object.
static tsi_result peer_from_x509(X509* cert, int include_certificate_type,
                                 tsi_peer* peer) {}

// Loads an in-memory PEM certificate chain into the SSL context.
static tsi_result ssl_ctx_use_certificate_chain(SSL_CTX* context,
                                                const char* pem_cert_chain,
                                                size_t pem_cert_chain_size) {}

#if !defined(OPENSSL_IS_BORINGSSL) && !defined(OPENSSL_NO_ENGINE)
static tsi_result ssl_ctx_use_engine_private_key(SSL_CTX* context,
                                                 const char* pem_key,
                                                 size_t pem_key_size) {
  tsi_result result = TSI_OK;
  EVP_PKEY* private_key = nullptr;
  ENGINE* engine = nullptr;
  char* engine_name = nullptr;
  // Parse key which is in following format engine:<engine_id>:<key_id>
  do {
    char* engine_start = (char*)pem_key + strlen(kSslEnginePrefix);
    char* engine_end = (char*)strchr(engine_start, ':');
    if (engine_end == nullptr) {
      result = TSI_INVALID_ARGUMENT;
      break;
    }
    char* key_id = engine_end + 1;
    int engine_name_length = engine_end - engine_start;
    if (engine_name_length == 0) {
      result = TSI_INVALID_ARGUMENT;
      break;
    }
    engine_name = static_cast<char*>(gpr_zalloc(engine_name_length + 1));
    memcpy(engine_name, engine_start, engine_name_length);
    gpr_log(GPR_DEBUG, "ENGINE key: %s", engine_name);
    ENGINE_load_dynamic();
    engine = ENGINE_by_id(engine_name);
    if (engine == nullptr) {
      // If not available at ENGINE_DIR, use dynamic to load from
      // current working directory.
      engine = ENGINE_by_id("dynamic");
      if (engine == nullptr) {
        gpr_log(GPR_ERROR, "Cannot load dynamic engine");
        result = TSI_INVALID_ARGUMENT;
        break;
      }
      if (!ENGINE_ctrl_cmd_string(engine, "ID", engine_name, 0) ||
          !ENGINE_ctrl_cmd_string(engine, "DIR_LOAD", "2", 0) ||
          !ENGINE_ctrl_cmd_string(engine, "DIR_ADD", ".", 0) ||
          !ENGINE_ctrl_cmd_string(engine, "LIST_ADD", "1", 0) ||
          !ENGINE_ctrl_cmd_string(engine, "LOAD", NULL, 0)) {
        gpr_log(GPR_ERROR, "Cannot find engine");
        result = TSI_INVALID_ARGUMENT;
        break;
      }
    }
    if (!ENGINE_set_default(engine, ENGINE_METHOD_ALL)) {
      gpr_log(GPR_ERROR, "ENGINE_set_default with ENGINE_METHOD_ALL failed");
      result = TSI_INVALID_ARGUMENT;
      break;
    }
    if (!ENGINE_init(engine)) {
      gpr_log(GPR_ERROR, "ENGINE_init failed");
      result = TSI_INVALID_ARGUMENT;
      break;
    }
    private_key = ENGINE_load_private_key(engine, key_id, 0, 0);
    if (private_key == nullptr) {
      gpr_log(GPR_ERROR, "ENGINE_load_private_key failed");
      result = TSI_INVALID_ARGUMENT;
      break;
    }
    if (!SSL_CTX_use_PrivateKey(context, private_key)) {
      gpr_log(GPR_ERROR, "SSL_CTX_use_PrivateKey failed");
      result = TSI_INVALID_ARGUMENT;
      break;
    }
  } while (0);
  if (engine != nullptr) ENGINE_free(engine);
  if (private_key != nullptr) EVP_PKEY_free(private_key);
  if (engine_name != nullptr) gpr_free(engine_name);
  return result;
}
#endif  // !defined(OPENSSL_IS_BORINGSSL) && !defined(OPENSSL_NO_ENGINE)

static tsi_result ssl_ctx_use_pem_private_key(SSL_CTX* context,
                                              const char* pem_key,
                                              size_t pem_key_size) {}

// Loads an in-memory PEM private key into the SSL context.
static tsi_result ssl_ctx_use_private_key(SSL_CTX* context, const char* pem_key,
                                          size_t pem_key_size) {}

// Loads in-memory PEM verification certs into the SSL context and optionally
// returns the verification cert names (root_names can be NULL).
static tsi_result x509_store_load_certs(X509_STORE* cert_store,
                                        const char* pem_roots,
                                        size_t pem_roots_size,
                                        STACK_OF(X509_NAME) * *root_names) {}

static tsi_result ssl_ctx_load_verification_certs(SSL_CTX* context,
                                                  const char* pem_roots,
                                                  size_t pem_roots_size,
                                                  STACK_OF(X509_NAME) *
                                                      *root_name) {}

// Populates the SSL context with a private key and a cert chain, and sets the
// cipher list and the ephemeral ECDH key.
static tsi_result populate_ssl_context(
    SSL_CTX* context, const tsi_ssl_pem_key_cert_pair* key_cert_pair,
    const char* cipher_list) {}

// Extracts the CN and the SANs from an X509 cert as a peer object.
tsi_result tsi_ssl_extract_x509_subject_names_from_pem_cert(
    const char* pem_cert, tsi_peer* peer) {}

// Builds the alpn protocol name list according to rfc 7301.
static tsi_result build_alpn_protocol_name_list(
    const char** alpn_protocols, uint16_t num_alpn_protocols,
    unsigned char** protocol_name_list, size_t* protocol_name_list_length) {}

// This callback is invoked when the CRL has been verified and will soft-fail
// errors in verification depending on certain error types.
static int verify_cb(int ok, X509_STORE_CTX* ctx) {}

// The verification callback is used for clients that don't really care about
// the server's certificate, but we need to pull it anyway, in case a higher
// layer wants to look at it. In this case the verification may fail, but
// we don't really care.
static int NullVerifyCallback(int /*preverify_ok*/, X509_STORE_CTX* /*ctx*/) {}

static int RootCertExtractCallback(int preverify_ok, X509_STORE_CTX* ctx) {}

// Sets the min and max TLS version of |ssl_context| to |min_tls_version| and
// |max_tls_version|, respectively. Calling this method is a no-op when using
// OpenSSL versions < 1.1.
static tsi_result tsi_set_min_and_max_tls_versions(
    SSL_CTX* ssl_context, tsi_tls_version min_tls_version,
    tsi_tls_version max_tls_version) {}

// --- tsi_ssl_root_certs_store methods implementation. ---

tsi_ssl_root_certs_store* tsi_ssl_root_certs_store_create(
    const char* pem_roots) {}

void tsi_ssl_root_certs_store_destroy(tsi_ssl_root_certs_store* self) {}

// --- tsi_ssl_session_cache methods implementation. ---

tsi_ssl_session_cache* tsi_ssl_session_cache_create_lru(size_t capacity) {}

void tsi_ssl_session_cache_ref(tsi_ssl_session_cache* cache) {}

void tsi_ssl_session_cache_unref(tsi_ssl_session_cache* cache) {}

// --- tsi_frame_protector methods implementation. ---

static tsi_result ssl_protector_protect(tsi_frame_protector* self,
                                        const unsigned char* unprotected_bytes,
                                        size_t* unprotected_bytes_size,
                                        unsigned char* protected_output_frames,
                                        size_t* protected_output_frames_size) {}

static tsi_result ssl_protector_protect_flush(
    tsi_frame_protector* self, unsigned char* protected_output_frames,
    size_t* protected_output_frames_size, size_t* still_pending_size) {}

static tsi_result ssl_protector_unprotect(
    tsi_frame_protector* self, const unsigned char* protected_frames_bytes,
    size_t* protected_frames_bytes_size, unsigned char* unprotected_bytes,
    size_t* unprotected_bytes_size) {}

static void ssl_protector_destroy(tsi_frame_protector* self) {}

static const tsi_frame_protector_vtable frame_protector_vtable =;

// --- tsi_server_handshaker_factory methods implementation. ---

static void tsi_ssl_handshaker_factory_destroy(
    tsi_ssl_handshaker_factory* factory) {}

static tsi_ssl_handshaker_factory* tsi_ssl_handshaker_factory_ref(
    tsi_ssl_handshaker_factory* factory) {}

static void tsi_ssl_handshaker_factory_unref(
    tsi_ssl_handshaker_factory* factory) {}

static tsi_ssl_handshaker_factory_vtable handshaker_factory_vtable =;

// Initializes a tsi_ssl_handshaker_factory object. Caller is responsible for
// allocating memory for the factory.
static void tsi_ssl_handshaker_factory_init(
    tsi_ssl_handshaker_factory* factory) {}

// Gets the X509 cert chain in PEM format as a tsi_peer_property.
tsi_result tsi_ssl_get_cert_chain_contents(STACK_OF(X509) * peer_chain,
                                           tsi_peer_property* property) {}

// --- tsi_handshaker_result methods implementation. ---
static tsi_result ssl_handshaker_result_extract_peer(
    const tsi_handshaker_result* self, tsi_peer* peer) {}

static tsi_result ssl_handshaker_result_get_frame_protector_type(
    const tsi_handshaker_result* /*self*/,
    tsi_frame_protector_type* frame_protector_type) {}

static tsi_result ssl_handshaker_result_create_frame_protector(
    const tsi_handshaker_result* self, size_t* max_output_protected_frame_size,
    tsi_frame_protector** protector) {}

static tsi_result ssl_handshaker_result_get_unused_bytes(
    const tsi_handshaker_result* self, const unsigned char** bytes,
    size_t* bytes_size) {}

static void ssl_handshaker_result_destroy(tsi_handshaker_result* self) {}

static const tsi_handshaker_result_vtable handshaker_result_vtable =;

static tsi_result ssl_handshaker_result_create(
    tsi_ssl_handshaker* handshaker, unsigned char* unused_bytes,
    size_t unused_bytes_size, tsi_handshaker_result** handshaker_result,
    std::string* error) {}

// --- tsi_handshaker methods implementation. ---

static tsi_result ssl_handshaker_get_bytes_to_send_to_peer(
    tsi_ssl_handshaker* impl, unsigned char* bytes, size_t* bytes_size,
    std::string* error) {}

static tsi_result ssl_handshaker_get_result(tsi_ssl_handshaker* impl) {}

static tsi_result ssl_handshaker_do_handshake(tsi_ssl_handshaker* impl,
                                              std::string* error) {}

static tsi_result ssl_handshaker_process_bytes_from_peer(
    tsi_ssl_handshaker* impl, const unsigned char* bytes, size_t* bytes_size,
    std::string* error) {}

static void ssl_handshaker_destroy(tsi_handshaker* self) {}

// Removes the bytes remaining in |impl->SSL|'s read BIO and writes them to
// |bytes_remaining|.
static tsi_result ssl_bytes_remaining(tsi_ssl_handshaker* impl,
                                      unsigned char** bytes_remaining,
                                      size_t* bytes_remaining_size,
                                      std::string* error) {}

// Write handshake data received from SSL to an unbound output buffer.
// By doing that, we drain SSL bio buffer used to hold handshake data.
// This API needs to be repeatedly called until all handshake data are
// received from SSL.
static tsi_result ssl_handshaker_write_output_buffer(tsi_handshaker* self,
                                                     size_t* bytes_written,
                                                     std::string* error) {}

static tsi_result ssl_handshaker_next(tsi_handshaker* self,
                                      const unsigned char* received_bytes,
                                      size_t received_bytes_size,
                                      const unsigned char** bytes_to_send,
                                      size_t* bytes_to_send_size,
                                      tsi_handshaker_result** handshaker_result,
                                      tsi_handshaker_on_next_done_cb /*cb*/,
                                      void* /*user_data*/, std::string* error) {}

static const tsi_handshaker_vtable handshaker_vtable =;

// --- tsi_ssl_handshaker_factory common methods. ---

static void tsi_ssl_handshaker_resume_session(
    SSL* ssl, tsi::SslSessionLRUCache* session_cache) {}

static tsi_result create_tsi_ssl_handshaker(SSL_CTX* ctx, int is_client,
                                            const char* server_name_indication,
                                            size_t network_bio_buf_size,
                                            size_t ssl_bio_buf_size,
                                            tsi_ssl_handshaker_factory* factory,
                                            tsi_handshaker** handshaker) {}

static int select_protocol_list(const unsigned char** out,
                                unsigned char* outlen,
                                const unsigned char* client_list,
                                size_t client_list_len,
                                const unsigned char* server_list,
                                size_t server_list_len) {}

// --- tsi_ssl_client_handshaker_factory methods implementation. ---

tsi_result tsi_ssl_client_handshaker_factory_create_handshaker(
    tsi_ssl_client_handshaker_factory* factory,
    const char* server_name_indication, size_t network_bio_buf_size,
    size_t ssl_bio_buf_size, tsi_handshaker** handshaker) {}

void tsi_ssl_client_handshaker_factory_unref(
    tsi_ssl_client_handshaker_factory* factory) {}

static void tsi_ssl_client_handshaker_factory_destroy(
    tsi_ssl_handshaker_factory* factory) {}

static int client_handshaker_factory_npn_callback(
    SSL* /*ssl*/, unsigned char** out, unsigned char* outlen,
    const unsigned char* in, unsigned int inlen, void* arg) {}

// --- tsi_ssl_server_handshaker_factory methods implementation. ---

tsi_result tsi_ssl_server_handshaker_factory_create_handshaker(
    tsi_ssl_server_handshaker_factory* factory, size_t network_bio_buf_size,
    size_t ssl_bio_buf_size, tsi_handshaker** handshaker) {}

void tsi_ssl_server_handshaker_factory_unref(
    tsi_ssl_server_handshaker_factory* factory) {}

static void tsi_ssl_server_handshaker_factory_destroy(
    tsi_ssl_handshaker_factory* factory) {}

static int does_entry_match_name(absl::string_view entry,
                                 absl::string_view name) {}

static int ssl_server_handshaker_factory_servername_callback(SSL* ssl,
                                                             int* /*ap*/,
                                                             void* arg) {}

#if TSI_OPENSSL_ALPN_SUPPORT
static int server_handshaker_factory_alpn_callback(
    SSL* /*ssl*/, const unsigned char** out, unsigned char* outlen,
    const unsigned char* in, unsigned int inlen, void* arg) {}
#endif  // TSI_OPENSSL_ALPN_SUPPORT

static int server_handshaker_factory_npn_advertised_callback(
    SSL* /*ssl*/, const unsigned char** out, unsigned int* outlen, void* arg) {}

/// This callback is called when new \a session is established and ready to
/// be cached. This session can be reused for new connections to similar
/// servers at later point of time.
/// It's intended to be used with SSL_CTX_sess_set_new_cb function.
///
/// It returns 1 if callback takes ownership over \a session and 0 otherwise.
static int server_handshaker_factory_new_session_callback(
    SSL* ssl, SSL_SESSION* session) {}

/// This callback is invoked at client or server when ssl/tls handshakes
/// complete and keylogging is enabled.
template <typename T>
static void ssl_keylogging_callback(const SSL* ssl, const char* info) {}

// --- tsi_ssl_handshaker_factory constructors. ---

static tsi_ssl_handshaker_factory_vtable client_handshaker_factory_vtable =;

tsi_result tsi_create_ssl_client_handshaker_factory(
    const tsi_ssl_pem_key_cert_pair* pem_key_cert_pair,
    const char* pem_root_certs, const char* cipher_suites,
    const char** alpn_protocols, uint16_t num_alpn_protocols,
    tsi_ssl_client_handshaker_factory** factory) {}

tsi_result tsi_create_ssl_client_handshaker_factory_with_options(
    const tsi_ssl_client_handshaker_options* options,
    tsi_ssl_client_handshaker_factory** factory) {}

static tsi_ssl_handshaker_factory_vtable server_handshaker_factory_vtable =;

tsi_result tsi_create_ssl_server_handshaker_factory(
    const tsi_ssl_pem_key_cert_pair* pem_key_cert_pairs,
    size_t num_key_cert_pairs, const char* pem_client_root_certs,
    int force_client_auth, const char* cipher_suites,
    const char** alpn_protocols, uint16_t num_alpn_protocols,
    tsi_ssl_server_handshaker_factory** factory) {}

tsi_result tsi_create_ssl_server_handshaker_factory_ex(
    const tsi_ssl_pem_key_cert_pair* pem_key_cert_pairs,
    size_t num_key_cert_pairs, const char* pem_client_root_certs,
    tsi_client_certificate_request_type client_certificate_request,
    const char* cipher_suites, const char** alpn_protocols,
    uint16_t num_alpn_protocols, tsi_ssl_server_handshaker_factory** factory) {}

tsi_result tsi_create_ssl_server_handshaker_factory_with_options(
    const tsi_ssl_server_handshaker_options* options,
    tsi_ssl_server_handshaker_factory** factory) {}

// --- tsi_ssl utils. ---

int tsi_ssl_peer_matches_name(const tsi_peer* peer, absl::string_view name) {}

// --- Testing support. ---
const tsi_ssl_handshaker_factory_vtable* tsi_ssl_handshaker_factory_swap_vtable(
    tsi_ssl_handshaker_factory* factory,
    tsi_ssl_handshaker_factory_vtable* new_vtable) {}