/*-------------------------------------------------------------------------
 *
 * be-secure-openssl.c
 *	  functions for OpenSSL support in the backend.
 *
 *
 * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/libpq/be-secure-openssl.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include <sys/stat.h>
#include <signal.h>
#include <fcntl.h>
#include <ctype.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>

#include "common/hashfn.h"
#include "common/string.h"
#include "libpq/libpq.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "storage/fd.h"
#include "storage/latch.h"
#include "utils/guc.h"
#include "utils/memutils.h"
#include "utils/wait_event.h"

/*
 * These SSL-related #includes must come after all system-provided headers.
 * This ensures that OpenSSL can take care of conflicts with Windows'
 * <wincrypt.h> by #undef'ing the conflicting macros.  (We don't directly
 * include <wincrypt.h>, but some other Windows headers do.)
 */
#include "common/openssl.h"
#include <openssl/bn.h>
#include <openssl/conf.h>
#include <openssl/dh.h>
#include <openssl/ec.h>
#include <openssl/x509v3.h>

/*
 * Simplehash for tracking configured hostnames to guard against duplicate
 * entries.  Each list of hosts is traversed and added to the hash during
 * parsing and if a duplicate error is detected an error will be thrown.
 */
typedef struct
{
	uint32		status;
	const char *hostname;
} HostCacheEntry;
static uint32 host_cache_pointer(const char *key);
#define SH_PREFIX		host_cache
#define SH_ELEMENT_TYPE	HostCacheEntry
#define SH_KEY_TYPE		const char *
#define SH_KEY			hostname
#define SH_HASH_KEY(tb, key)	host_cache_pointer(key)
#define SH_EQUAL(tb, a, b)		(pg_strcasecmp(a, b) == 0)
#define SH_SCOPE				static inline
#define SH_DECLARE
#define SH_DEFINE
#include "lib/simplehash.h"

/* default init hook can be overridden by a shared library */
static void default_openssl_tls_init(SSL_CTX *context, bool isServerStart);
openssl_tls_init_hook_typ openssl_tls_init_hook = default_openssl_tls_init;

static int	port_bio_read(BIO *h, char *buf, int size);
static int	port_bio_write(BIO *h, const char *buf, int size);
static BIO_METHOD *port_bio_method(void);
static int	ssl_set_port_bio(Port *port);

static DH  *load_dh_file(char *filename, bool isServerStart);
static DH  *load_dh_buffer(const char *buffer, size_t len);
static int	ssl_external_passwd_cb(char *buf, int size, int rwflag, void *userdata);
static int	dummy_ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata);
static int	verify_cb(int ok, X509_STORE_CTX *ctx);
static void info_cb(const SSL *ssl, int type, int args);
static int	alpn_cb(SSL *ssl,
					const unsigned char **out,
					unsigned char *outlen,
					const unsigned char *in,
					unsigned int inlen,
					void *userdata);
static bool initialize_dh(SSL_CTX *context, bool isServerStart);
static bool initialize_ecdh(SSL_CTX *context, bool isServerStart);
static const char *SSLerrmessageExt(unsigned long ecode, const char *replacement);
static const char *SSLerrmessage(unsigned long ecode);
static bool init_host_context(HostsLine *host, bool isServerStart);
static void host_context_cleanup_cb(void *arg);
#ifdef HAVE_SSL_CTX_SET_CLIENT_HELLO_CB
static int	sni_clienthello_cb(SSL *ssl, int *al, void *arg);
#endif

static char *X509_NAME_to_cstring(const X509_NAME *name);

static SSL_CTX *SSL_context = NULL;
static MemoryContext SSL_hosts_memcxt = NULL;
static struct hosts
{
	/*
	 * List of HostsLine structures containing SSL configurations for
	 * connections with hostnames defined in the SNI extension.
	 */
	List	   *sni;

	/* The SSL configuration to use for connections without SNI */
	HostsLine  *no_sni;

	/*
	 * The default SSL configuration to use as a fallback in case no hostname
	 * matches the supplied hostname in the SNI extension.
	 */
	HostsLine  *default_host;
}		   *SSL_hosts;

static bool dummy_ssl_passwd_cb_called = false;
static bool ssl_is_server_start;

static int	ssl_protocol_version_to_openssl(int v);
static const char *ssl_protocol_version_to_string(int v);

struct CallbackErr
{
	/*
	 * Storage for passing certificate verification error logging from the
	 * callback.
	 */
	char	   *cert_errdetail;
};

/* ------------------------------------------------------------ */
/*						 Public interface						*/
/* ------------------------------------------------------------ */

int
be_tls_init(bool isServerStart)
{
	List	   *pg_hosts = NIL;
	ListCell   *line;
	MemoryContext oldcxt;
	MemoryContext host_memcxt = NULL;
	MemoryContextCallback *host_memcxt_cb;
	char	   *err_msg = NULL;
	HostsFileLoadResult res;
	struct hosts *new_hosts;
	SSL_CTX    *context = NULL;
	int			ssl_ver_min = -1;
	int			ssl_ver_max = -1;
	host_cache_hash *host_cache = NULL;

	/*
	 * Since we don't know which host we're using until the ClientHello is
	 * sent, ssl_loaded_verify_locations *always* starts out as false. The
	 * only place it's set to true is in sni_clienthello_cb().
	 */
	ssl_loaded_verify_locations = false;

	host_memcxt = AllocSetContextCreate(CurrentMemoryContext,
										"hosts file parser context",
										ALLOCSET_SMALL_SIZES);
	oldcxt = MemoryContextSwitchTo(host_memcxt);

	/* Allocate a tentative replacement for SSL_hosts. */
	new_hosts = palloc0_object(struct hosts);

	/*
	 * Register a reset callback for the memory context which is responsible
	 * for freeing OpenSSL managed allocations upon context deletion.  The
	 * callback is allocated here to make sure it gets cleaned up along with
	 * the memory context it's registered for.
	 */
	host_memcxt_cb = palloc0_object(MemoryContextCallback);
	host_memcxt_cb->func = host_context_cleanup_cb;
	host_memcxt_cb->arg = new_hosts;
	MemoryContextRegisterResetCallback(host_memcxt, host_memcxt_cb);

	/*
	 * If ssl_sni is enabled, attempt to load and parse TLS configuration from
	 * the pg_hosts.conf file with the set of hosts returned as a list.  If
	 * there are hosts configured they take precedence over the configuration
	 * in postgresql.conf.  Make sure to allocate the parsed rows in their own
	 * memory context so that we can delete them easily in case parsing fails.
	 * If ssl_sni is disabled then set the state accordingly to make sure we
	 * instead parse the config from postgresql.conf.
	 *
	 * The reason for not doing everything in this if-else conditional is that
	 * we want to use the same processing of postgresql.conf for when ssl_sni
	 * is off as well as when it's on but the hosts file is missing etc.  Thus
	 * we set res to the state and continue with a new conditional instead of
	 * duplicating logic and risk it diverging over time.
	 */
	if (ssl_sni)
	{
		/*
		 * The GUC check hook should have already blocked this but to be on
		 * the safe side we double-check here.
		 */
#ifndef HAVE_SSL_CTX_SET_CLIENT_HELLO_CB
		ereport(isServerStart ? FATAL : LOG,
				errcode(ERRCODE_CONFIG_FILE_ERROR),
				errmsg("ssl_sni is not supported with LibreSSL"));
		goto error;
#endif

		/* Attempt to load configuration from pg_hosts.conf */
		res = load_hosts(&pg_hosts, &err_msg);

		/*
		 * pg_hosts.conf is not required to contain configuration, but if it
		 * does we error out in case it fails to load rather than continue to
		 * try the postgresql.conf configuration to avoid silently falling
		 * back on an undesired configuration.
		 */
		if (res == HOSTSFILE_LOAD_FAILED)
		{
			ereport(isServerStart ? FATAL : LOG,
					errcode(ERRCODE_CONFIG_FILE_ERROR),
					errmsg("could not load \"%s\": %s", HostsFileName,
						   err_msg ? err_msg : "unknown error"));
			goto error;
		}
	}
	else
		res = HOSTSFILE_DISABLED;

	/*
	 * Loading and parsing the hosts file was successful, create configs for
	 * each host entry and add to the list of hosts to be checked during
	 * login.
	 */
	if (res == HOSTSFILE_LOAD_OK)
	{
		Assert(ssl_sni);

		foreach(line, pg_hosts)
		{
			HostsLine  *host = lfirst(line);

			if (!init_host_context(host, isServerStart))
				goto error;

			/*
			 * The hostname in the config will be set to NULL for the default
			 * host as well as in configs used for non-SNI connections.  Lists
			 * of hostnames in pg_hosts.conf are not allowed to contain the
			 * default '*' entry or a '/no_sni/' entry and this is checked
			 * during parsing.  Thus we can inspect the head of the hostnames
			 * list for these since they will never be anywhere else.
			 */
			if (strcmp(linitial(host->hostnames), "*") == 0)
			{
				if (new_hosts->default_host)
				{
					ereport(isServerStart ? FATAL : LOG,
							errcode(ERRCODE_CONFIG_FILE_ERROR),
							errmsg("multiple default hosts specified"),
							errcontext("line %d of configuration file \"%s\"",
									   host->linenumber, host->sourcefile));
					goto error;
				}

				new_hosts->default_host = host;
			}
			else if (strcmp(linitial(host->hostnames), "/no_sni/") == 0)
			{
				if (new_hosts->no_sni)
				{
					ereport(isServerStart ? FATAL : LOG,
							errcode(ERRCODE_CONFIG_FILE_ERROR),
							errmsg("multiple no_sni hosts specified"),
							errcontext("line %d of configuration file \"%s\"",
									   host->linenumber, host->sourcefile));
					goto error;
				}

				new_hosts->no_sni = host;
			}
			else
			{
				/* Check the hostnames for duplicates */
				if (!host_cache)
					host_cache = host_cache_create(host_memcxt, 32, NULL);

				foreach_ptr(char, hostname, host->hostnames)
				{
					HostCacheEntry *entry;
					bool		found;

					entry = host_cache_insert(host_cache, hostname, &found);
					if (found)
					{
						ereport(isServerStart ? FATAL : LOG,
								errcode(ERRCODE_CONFIG_FILE_ERROR),
								errmsg("multiple entries for host \"%s\" specified",
									   hostname),
								errcontext("line %d of configuration file \"%s\"",
										   host->linenumber, host->sourcefile));
						goto error;
					}
					else
						entry->hostname = pstrdup(hostname);
				}

				/*
				 * At this point we know we have a configuration with a list
				 * of distinct 1..n hostnames for literal string matching with
				 * the SNI extension from the user.
				 */
				new_hosts->sni = lappend(new_hosts->sni, host);
			}
		}
	}

	/*
	 * If SNI is disabled, then we load configuration from postgresql.conf. If
	 * SNI is enabled but the pg_hosts.conf file doesn't exist, or is empty,
	 * then we also load the config from postgresql.conf.
	 */
	else if (res == HOSTSFILE_DISABLED || res == HOSTSFILE_EMPTY || res == HOSTSFILE_MISSING)
	{
		HostsLine  *pgconf = palloc0(sizeof(HostsLine));

#ifdef USE_ASSERT_CHECKING
		if (res == HOSTSFILE_DISABLED)
			Assert(ssl_sni == false);
#endif

		pgconf->ssl_cert = ssl_cert_file;
		pgconf->ssl_key = ssl_key_file;
		pgconf->ssl_ca = ssl_ca_file;
		pgconf->ssl_passphrase_cmd = ssl_passphrase_command;
		pgconf->ssl_passphrase_reload = ssl_passphrase_command_supports_reload;

		if (!init_host_context(pgconf, isServerStart))
			goto error;

		/*
		 * If postgresql.conf is used to configure SSL then by definition it
		 * will be the default context as we don't have per-host config.
		 */
		new_hosts->default_host = pgconf;
	}

	/*
	 * Make sure we have at least one configuration loaded to use, without
	 * that we cannot drive a connection so exit.
	 */
	if (new_hosts->sni == NIL && !new_hosts->default_host && !new_hosts->no_sni)
	{
		ereport(isServerStart ? FATAL : LOG,
				errcode(ERRCODE_CONFIG_FILE_ERROR),
				errmsg("no SSL configurations loaded"),
		/*- translator: The two %s contain filenames */
				errhint("If ssl_sni is enabled then add configuration to \"%s\", else \"%s\"",
						HostsFileName, "postgresql.conf"));
		goto error;
	}

#ifdef HAVE_SSL_CTX_SET_CLIENT_HELLO_CB

	/*
	 * Create a new SSL context into which we'll load all the configuration
	 * settings.  If we fail partway through, we can avoid memory leakage by
	 * freeing this context; we don't install it as active until the end.
	 *
	 * We use SSLv23_method() because it can negotiate use of the highest
	 * mutually supported protocol version, while alternatives like
	 * TLSv1_2_method() permit only one specific version.  Note that we don't
	 * actually allow SSL v2 or v3, only TLS protocols (see below).
	 */
	context = SSL_CTX_new(SSLv23_method());
	if (!context)
	{
		ereport(isServerStart ? FATAL : LOG,
				(errmsg("could not create SSL context: %s",
						SSLerrmessage(ERR_get_error()))));
		goto error;
	}
#else

	/*
	 * If the client hello callback isn't supported we want to use the default
	 * context as the one to drive the handshake so avoid creating a new one
	 * and use the already existing default one instead.
	 */
	context = new_hosts->default_host->ssl_ctx;

	/*
	 * Since we don't allocate a new SSL_CTX here like we do when SNI has been
	 * enabled we need to bump the reference count on context to avoid double
	 * free of the context when using the same cleanup logic across the cases.
	 */
	SSL_CTX_up_ref(context);
#endif

	/*
	 * Disable OpenSSL's moving-write-buffer sanity check, because it causes
	 * unnecessary failures in nonblocking send cases.
	 */
	SSL_CTX_set_mode(context, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);

	if (ssl_min_protocol_version)
	{
		ssl_ver_min = ssl_protocol_version_to_openssl(ssl_min_protocol_version);

		if (ssl_ver_min == -1)
		{
			ereport(isServerStart ? FATAL : LOG,
			/*- translator: first %s is a GUC option name, second %s is its value */
					(errmsg("\"%s\" setting \"%s\" not supported by this build",
							"ssl_min_protocol_version",
							GetConfigOption("ssl_min_protocol_version",
											false, false))));
			goto error;
		}

		if (!SSL_CTX_set_min_proto_version(context, ssl_ver_min))
		{
			ereport(isServerStart ? FATAL : LOG,
					(errmsg("could not set minimum SSL protocol version")));
			goto error;
		}
	}

	if (ssl_max_protocol_version)
	{
		ssl_ver_max = ssl_protocol_version_to_openssl(ssl_max_protocol_version);

		if (ssl_ver_max == -1)
		{
			ereport(isServerStart ? FATAL : LOG,
			/*- translator: first %s is a GUC option name, second %s is its value */
					(errmsg("\"%s\" setting \"%s\" not supported by this build",
							"ssl_max_protocol_version",
							GetConfigOption("ssl_max_protocol_version",
											false, false))));
			goto error;
		}

		if (!SSL_CTX_set_max_proto_version(context, ssl_ver_max))
		{
			ereport(isServerStart ? FATAL : LOG,
					(errmsg("could not set maximum SSL protocol version")));
			goto error;
		}
	}

	/* Check compatibility of min/max protocols */
	if (ssl_min_protocol_version &&
		ssl_max_protocol_version)
	{
		/*
		 * No need to check for invalid values (-1) for each protocol number
		 * as the code above would have already generated an error.
		 */
		if (ssl_ver_min > ssl_ver_max)
		{
			ereport(isServerStart ? FATAL : LOG,
					(errcode(ERRCODE_CONFIG_FILE_ERROR),
					 errmsg("could not set SSL protocol version range"),
					 errdetail("\"%s\" cannot be higher than \"%s\".",
							   "ssl_min_protocol_version",
							   "ssl_max_protocol_version")));
			goto error;
		}
	}

	/*
	 * Disallow SSL session tickets. OpenSSL use both stateful and stateless
	 * tickets for TLSv1.3, and stateless ticket for TLSv1.2. SSL_OP_NO_TICKET
	 * is available since 0.9.8f but only turns off stateless tickets. In
	 * order to turn off stateful tickets we need SSL_CTX_set_num_tickets,
	 * which is available since OpenSSL 1.1.1.  LibreSSL 3.5.4 (from OpenBSD
	 * 7.1) introduced this API for compatibility, but doesn't support session
	 * tickets at all so it's a no-op there.
	 */
#ifdef HAVE_SSL_CTX_SET_NUM_TICKETS
	SSL_CTX_set_num_tickets(context, 0);
#endif
	SSL_CTX_set_options(context, SSL_OP_NO_TICKET);

	/* disallow SSL session caching, too */
	SSL_CTX_set_session_cache_mode(context, SSL_SESS_CACHE_OFF);

	/* disallow SSL compression */
	SSL_CTX_set_options(context, SSL_OP_NO_COMPRESSION);

	/*
	 * Disallow SSL renegotiation.  This concerns only TLSv1.2 and older
	 * protocol versions, as TLSv1.3 has no support for renegotiation.
	 * SSL_OP_NO_RENEGOTIATION is available in OpenSSL since 1.1.0h (via a
	 * backport from 1.1.1). SSL_OP_NO_CLIENT_RENEGOTIATION is available in
	 * LibreSSL since 2.5.1 disallowing all client-initiated renegotiation
	 * (this is usually on by default).
	 */
#ifdef SSL_OP_NO_RENEGOTIATION
	SSL_CTX_set_options(context, SSL_OP_NO_RENEGOTIATION);
#endif
#ifdef SSL_OP_NO_CLIENT_RENEGOTIATION
	SSL_CTX_set_options(context, SSL_OP_NO_CLIENT_RENEGOTIATION);
#endif

	/* set up ephemeral DH and ECDH keys */
	if (!initialize_dh(context, isServerStart))
		goto error;
	if (!initialize_ecdh(context, isServerStart))
		goto error;

	/* set up the allowed cipher list for TLSv1.2 and below */
	if (SSL_CTX_set_cipher_list(context, SSLCipherList) != 1)
	{
		ereport(isServerStart ? FATAL : LOG,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("could not set the TLSv1.2 cipher list (no valid ciphers available)")));
		goto error;
	}

	/*
	 * Set up the allowed cipher suites for TLSv1.3. If the GUC is an empty
	 * string we leave the allowed suites to be the OpenSSL default value.
	 */
	if (SSLCipherSuites[0])
	{
		/* set up the allowed cipher suites */
		if (SSL_CTX_set_ciphersuites(context, SSLCipherSuites) != 1)
		{
			ereport(isServerStart ? FATAL : LOG,
					(errcode(ERRCODE_CONFIG_FILE_ERROR),
					 errmsg("could not set the TLSv1.3 cipher suites (no valid ciphers available)")));
			goto error;
		}
	}

	/* Let server choose order */
	if (SSLPreferServerCiphers)
		SSL_CTX_set_options(context, SSL_OP_CIPHER_SERVER_PREFERENCE);

	/*
	 * Success!  Replace any existing SSL_context and host configurations.
	 */
	if (SSL_context)
	{
		SSL_CTX_free(SSL_context);
		SSL_context = NULL;
	}

	MemoryContextSwitchTo(oldcxt);

	if (SSL_hosts_memcxt)
		MemoryContextDelete(SSL_hosts_memcxt);

	SSL_hosts_memcxt = host_memcxt;
	SSL_hosts = new_hosts;
	SSL_context = context;

	return 0;

	/*
	 * Clean up by releasing working SSL contexts as well as allocations
	 * performed during parsing.  Since all our allocations are done in a
	 * local memory context all we need to do is delete it.
	 */
error:
	if (context)
		SSL_CTX_free(context);

	MemoryContextSwitchTo(oldcxt);
	MemoryContextDelete(host_memcxt);
	return -1;
}

/*
 * host_context_cleanup_cb
 *
 * Memory context reset callback for clearing OpenSSL managed resources when
 * hosts are reloaded and the previous set of configured hosts are freed. As
 * all hosts are allocated in a single context we don't need to free each host
 * individually, just resources managed by OpenSSL.
 */
static void
host_context_cleanup_cb(void *arg)
{
	struct hosts *hosts = arg;

	foreach_ptr(HostsLine, host, hosts->sni)
	{
		if (host->ssl_ctx != NULL)
			SSL_CTX_free(host->ssl_ctx);
	}

	if (hosts->no_sni && hosts->no_sni->ssl_ctx)
		SSL_CTX_free(hosts->no_sni->ssl_ctx);

	if (hosts->default_host && hosts->default_host->ssl_ctx)
		SSL_CTX_free(hosts->default_host->ssl_ctx);
}

static bool
init_host_context(HostsLine *host, bool isServerStart)
{
	SSL_CTX    *ctx = SSL_CTX_new(SSLv23_method());
	static bool init_warned = false;

	if (!ctx)
	{
		ereport(isServerStart ? FATAL : LOG,
				(errmsg("could not create SSL context: %s",
						SSLerrmessage(ERR_get_error()))));
		goto error;
	}

	/*
	 * Call init hook (usually to set password callback) in case SNI hasn't
	 * been enabled. If SNI is enabled the hook won't operate on the actual
	 * TLS context used so it cannot function properly; we warn if one has
	 * been installed.
	 *
	 * If SNI is enabled, we set password callback based what was configured.
	 */
	if (!ssl_sni)
		(*openssl_tls_init_hook) (ctx, isServerStart);
	else
	{
		if (openssl_tls_init_hook != default_openssl_tls_init && !init_warned)
		{
			ereport(WARNING,
					errcode(ERRCODE_CONFIG_FILE_ERROR),
					errmsg("SNI is enabled; installed TLS init hook will be ignored"),
			/*- translator: first %s is a GUC, second %s contains a filename */
					errhint("TLS init hooks are incompatible with SNI. "
							"Set \"%s\" to \"off\" to make use of the hook "
							"that is currently installed, or remove the hook "
							"and use per-host passphrase commands in \"%s\".",
							"ssl_sni", HostsFileName));
			init_warned = true;
		}

		/*
		 * Set up the password callback, if configured.
		 */
		if (isServerStart)
		{
			if (host->ssl_passphrase_cmd && host->ssl_passphrase_cmd[0])
			{
				SSL_CTX_set_default_passwd_cb(ctx, ssl_external_passwd_cb);
				SSL_CTX_set_default_passwd_cb_userdata(ctx, host->ssl_passphrase_cmd);
			}
		}
		else
		{
			/*
			 * If ssl_passphrase_reload is true then ssl_passphrase_cmd cannot
			 * be NULL due to their parsing order, but just in case and to
			 * self-document the code we replicate the nullness checks.
			 */
			if (host->ssl_passphrase_reload &&
				(host->ssl_passphrase_cmd && host->ssl_passphrase_cmd[0]))
			{
				SSL_CTX_set_default_passwd_cb(ctx, ssl_external_passwd_cb);
				SSL_CTX_set_default_passwd_cb_userdata(ctx, host->ssl_passphrase_cmd);
			}
			else
			{
				/*
				 * If reloading and no external command is configured,
				 * override OpenSSL's default handling of passphrase-protected
				 * files, because we don't want to prompt for a passphrase in
				 * an already-running server.
				 */
				SSL_CTX_set_default_passwd_cb(ctx, dummy_ssl_passwd_cb);
			}
		}
	}

	/*
	 * Load and verify server's certificate and private key
	 */
	if (SSL_CTX_use_certificate_chain_file(ctx, host->ssl_cert) != 1)
	{
		ereport(isServerStart ? FATAL : LOG,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("could not load server certificate file \"%s\": %s",
						host->ssl_cert, SSLerrmessage(ERR_get_error()))));
		goto error;
	}

	if (!check_ssl_key_file_permissions(host->ssl_key, isServerStart))
		goto error;


	/* used by the callback */
	ssl_is_server_start = isServerStart;

	/*
	 * OK, try to load the private key file.
	 */
	dummy_ssl_passwd_cb_called = false;

	if (SSL_CTX_use_PrivateKey_file(ctx,
									host->ssl_key,
									SSL_FILETYPE_PEM) != 1)
	{
		if (dummy_ssl_passwd_cb_called)
			ereport(isServerStart ? FATAL : LOG,
					(errcode(ERRCODE_CONFIG_FILE_ERROR),
					 errmsg("private key file \"%s\" cannot be reloaded because it requires a passphrase",
							host->ssl_key)));
		else
			ereport(isServerStart ? FATAL : LOG,
					(errcode(ERRCODE_CONFIG_FILE_ERROR),
					 errmsg("could not load private key file \"%s\": %s",
							host->ssl_key, SSLerrmessage(ERR_get_error()))));
		goto error;
	}

	if (SSL_CTX_check_private_key(ctx) != 1)
	{
		ereport(isServerStart ? FATAL : LOG,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("check of private key failed: %s",
						SSLerrmessage(ERR_get_error()))));
		goto error;
	}

	/*
	 * Load CA store, so we can verify client certificates if needed.
	 */
	if (host->ssl_ca && host->ssl_ca[0])
	{
		STACK_OF(X509_NAME) * root_cert_list;

		if (SSL_CTX_load_verify_locations(ctx, host->ssl_ca, NULL) != 1 ||
			(root_cert_list = SSL_load_client_CA_file(host->ssl_ca)) == NULL)
		{
			ereport(isServerStart ? FATAL : LOG,
					(errcode(ERRCODE_CONFIG_FILE_ERROR),
					 errmsg("could not load root certificate file \"%s\": %s",
							host->ssl_ca, SSLerrmessage(ERR_get_error()))));
			goto error;
		}

		/*
		 * Tell OpenSSL to send the list of root certs we trust to clients in
		 * CertificateRequests.  This lets a client with a keystore select the
		 * appropriate client certificate to send to us.  Also, this ensures
		 * that the SSL context will "own" the root_cert_list and remember to
		 * free it when no longer needed.
		 */
		SSL_CTX_set_client_CA_list(ctx, root_cert_list);
	}

	/*----------
	 * Load the Certificate Revocation List (CRL).
	 * http://searchsecurity.techtarget.com/sDefinition/0,,sid14_gci803160,00.html
	 *----------
	 */
	if (ssl_crl_file[0] || ssl_crl_dir[0])
	{
		X509_STORE *cvstore = SSL_CTX_get_cert_store(ctx);

		if (cvstore)
		{
			/* Set the flags to check against the complete CRL chain */
			if (X509_STORE_load_locations(cvstore,
										  ssl_crl_file[0] ? ssl_crl_file : NULL,
										  ssl_crl_dir[0] ? ssl_crl_dir : NULL)
				== 1)
			{
				X509_STORE_set_flags(cvstore,
									 X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL);
			}
			else if (ssl_crl_dir[0] == 0)
			{
				ereport(isServerStart ? FATAL : LOG,
						(errcode(ERRCODE_CONFIG_FILE_ERROR),
						 errmsg("could not load SSL certificate revocation list file \"%s\": %s",
								ssl_crl_file, SSLerrmessage(ERR_get_error()))));
				goto error;
			}
			else if (ssl_crl_file[0] == 0)
			{
				ereport(isServerStart ? FATAL : LOG,
						(errcode(ERRCODE_CONFIG_FILE_ERROR),
						 errmsg("could not load SSL certificate revocation list directory \"%s\": %s",
								ssl_crl_dir, SSLerrmessage(ERR_get_error()))));
				goto error;
			}
			else
			{
				ereport(isServerStart ? FATAL : LOG,
						(errcode(ERRCODE_CONFIG_FILE_ERROR),
						 errmsg("could not load SSL certificate revocation list file \"%s\" or directory \"%s\": %s",
								ssl_crl_file, ssl_crl_dir,
								SSLerrmessage(ERR_get_error()))));
				goto error;
			}
		}
	}

	host->ssl_ctx = ctx;
	return true;

error:
	if (ctx)
		SSL_CTX_free(ctx);
	return false;
}

void
be_tls_destroy(void)
{
	if (SSL_context)
		SSL_CTX_free(SSL_context);
	SSL_context = NULL;
	ssl_loaded_verify_locations = false;
}

int
be_tls_open_server(Port *port)
{
	int			r;
	int			err;
	int			waitfor;
	unsigned long ecode;
	bool		give_proto_hint;
	static struct CallbackErr err_context;

	Assert(!port->ssl);
	Assert(!port->peer);

	if (!SSL_context)
	{
		ereport(COMMERROR,
				(errcode(ERRCODE_PROTOCOL_VIOLATION),
				 errmsg("could not initialize SSL connection: SSL context not set up")));
		return -1;
	}

	/* set up debugging/info callback */
	SSL_CTX_set_info_callback(SSL_context, info_cb);

	/* enable ALPN */
	SSL_CTX_set_alpn_select_cb(SSL_context, alpn_cb, port);

	if (!(port->ssl = SSL_new(SSL_context)))
	{
		ereport(COMMERROR,
				(errcode(ERRCODE_PROTOCOL_VIOLATION),
				 errmsg("could not initialize SSL connection: %s",
						SSLerrmessage(ERR_get_error()))));
		return -1;
	}
	if (!ssl_set_port_bio(port))
	{
		ereport(COMMERROR,
				(errcode(ERRCODE_PROTOCOL_VIOLATION),
				 errmsg("could not set SSL socket: %s",
						SSLerrmessage(ERR_get_error()))));
		return -1;
	}

	/*
	 * If the underlying TLS library supports the client hello callback we use
	 * that in order to support host based configuration using the SNI TLS
	 * extension.  If the user has disabled SNI via the ssl_sni GUC we still
	 * make use of the callback in order to have consistent handling of
	 * OpenSSL contexts, except in that case the callback will install the
	 * default configuration regardless of the hostname sent by the user in
	 * the handshake.
	 *
	 * In case the TLS library does not support the client hello callback, as
	 * of this writing LibreSSL does not, we need to install the client cert
	 * verification callback here (if the user configured a CA) since we
	 * cannot use the OpenSSL context update functionality.
	 */
#ifdef HAVE_SSL_CTX_SET_CLIENT_HELLO_CB
	SSL_CTX_set_client_hello_cb(SSL_context, sni_clienthello_cb, NULL);
#else
	if (SSL_hosts->default_host->ssl_ca && SSL_hosts->default_host->ssl_ca[0])
	{
		/*
		 * Always ask for SSL client cert, but don't fail if it's not
		 * presented.  We might fail such connections later, depending on what
		 * we find in pg_hba.conf.
		 */
		SSL_set_verify(port->ssl,
					   (SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE),
					   verify_cb);

		ssl_loaded_verify_locations = true;
	}
#endif

	err_context.cert_errdetail = NULL;
	SSL_set_ex_data(port->ssl, 0, &err_context);

	port->ssl_in_use = true;

aloop:

	/*
	 * Prepare to call SSL_get_error() by clearing thread's OpenSSL error
	 * queue.  In general, the current thread's error queue must be empty
	 * before the TLS/SSL I/O operation is attempted, or SSL_get_error() will
	 * not work reliably.  An extension may have failed to clear the
	 * per-thread error queue following another call to an OpenSSL I/O
	 * routine.
	 */
	errno = 0;
	ERR_clear_error();
	r = SSL_accept(port->ssl);
	if (r <= 0)
	{
		err = SSL_get_error(port->ssl, r);

		/*
		 * Other clients of OpenSSL in the backend may fail to call
		 * ERR_get_error(), but we always do, so as to not cause problems for
		 * OpenSSL clients that don't call ERR_clear_error() defensively.  Be
		 * sure that this happens by calling now. SSL_get_error() relies on
		 * the OpenSSL per-thread error queue being intact, so this is the
		 * earliest possible point ERR_get_error() may be called.
		 */
		ecode = ERR_get_error();
		switch (err)
		{
			case SSL_ERROR_WANT_READ:
			case SSL_ERROR_WANT_WRITE:
				/* not allowed during connection establishment */
				Assert(!port->noblock);

				/*
				 * No need to care about timeouts/interrupts here. At this
				 * point authentication_timeout still employs
				 * StartupPacketTimeoutHandler() which directly exits.
				 */
				if (err == SSL_ERROR_WANT_READ)
					waitfor = WL_SOCKET_READABLE | WL_EXIT_ON_PM_DEATH;
				else
					waitfor = WL_SOCKET_WRITEABLE | WL_EXIT_ON_PM_DEATH;

				(void) WaitLatchOrSocket(NULL, waitfor, port->sock, 0,
										 WAIT_EVENT_SSL_OPEN_SERVER);
				goto aloop;
			case SSL_ERROR_SYSCALL:
				if (r < 0 && errno != 0)
					ereport(COMMERROR,
							(errcode_for_socket_access(),
							 errmsg("could not accept SSL connection: %m")));
				else
					ereport(COMMERROR,
							(errcode(ERRCODE_PROTOCOL_VIOLATION),
							 errmsg("could not accept SSL connection: EOF detected")));
				break;
			case SSL_ERROR_SSL:
				switch (ERR_GET_REASON(ecode))
				{
						/*
						 * UNSUPPORTED_PROTOCOL, WRONG_VERSION_NUMBER, and
						 * TLSV1_ALERT_PROTOCOL_VERSION have been observed
						 * when trying to communicate with an old OpenSSL
						 * library, or when the client and server specify
						 * disjoint protocol ranges.  NO_PROTOCOLS_AVAILABLE
						 * occurs if there's a local misconfiguration (which
						 * can happen despite our checks, if openssl.cnf
						 * injects a limit we didn't account for).  It's not
						 * very clear what would make OpenSSL return the other
						 * codes listed here, but a hint about protocol
						 * versions seems like it's appropriate for all.
						 */
					case SSL_R_NO_PROTOCOLS_AVAILABLE:
					case SSL_R_UNSUPPORTED_PROTOCOL:
					case SSL_R_BAD_PROTOCOL_VERSION_NUMBER:
					case SSL_R_UNKNOWN_PROTOCOL:
					case SSL_R_UNKNOWN_SSL_VERSION:
					case SSL_R_UNSUPPORTED_SSL_VERSION:
					case SSL_R_WRONG_SSL_VERSION:
					case SSL_R_WRONG_VERSION_NUMBER:
					case SSL_R_TLSV1_ALERT_PROTOCOL_VERSION:
#ifdef SSL_R_VERSION_TOO_HIGH
					case SSL_R_VERSION_TOO_HIGH:
#endif
#ifdef SSL_R_VERSION_TOO_LOW
					case SSL_R_VERSION_TOO_LOW:
#endif
						give_proto_hint = true;
						break;
					default:
						give_proto_hint = false;
						break;
				}
				ereport(COMMERROR,
						(errcode(ERRCODE_PROTOCOL_VIOLATION),
						 errmsg("could not accept SSL connection: %s",
								SSLerrmessage(ecode)),
						 err_context.cert_errdetail ? errdetail_internal("%s", err_context.cert_errdetail) : 0,
						 give_proto_hint ?
						 errhint("This may indicate that the client does not support any SSL protocol version between %s and %s.",
								 ssl_min_protocol_version ?
								 ssl_protocol_version_to_string(ssl_min_protocol_version) :
								 MIN_OPENSSL_TLS_VERSION,
								 ssl_max_protocol_version ?
								 ssl_protocol_version_to_string(ssl_max_protocol_version) :
								 MAX_OPENSSL_TLS_VERSION) : 0));
				if (err_context.cert_errdetail)
					pfree(err_context.cert_errdetail);
				break;
			case SSL_ERROR_ZERO_RETURN:
				ereport(COMMERROR,
						(errcode(ERRCODE_PROTOCOL_VIOLATION),
						 errmsg("could not accept SSL connection: EOF detected")));
				break;
			default:
				ereport(COMMERROR,
						(errcode(ERRCODE_PROTOCOL_VIOLATION),
						 errmsg("unrecognized SSL error code: %d",
								err)));
				break;
		}
		return -1;
	}

	/* Get the protocol selected by ALPN */
	port->alpn_used = false;
	{
		const unsigned char *selected;
		unsigned int len;

		SSL_get0_alpn_selected(port->ssl, &selected, &len);

		/* If ALPN is used, check that we negotiated the expected protocol */
		if (selected != NULL)
		{
			if (len == strlen(PG_ALPN_PROTOCOL) &&
				memcmp(selected, PG_ALPN_PROTOCOL, strlen(PG_ALPN_PROTOCOL)) == 0)
			{
				port->alpn_used = true;
			}
			else
			{
				/* shouldn't happen */
				ereport(COMMERROR,
						(errcode(ERRCODE_PROTOCOL_VIOLATION),
						 errmsg("received SSL connection request with unexpected ALPN protocol")));
			}
		}
	}

	/* Get client certificate, if available. */
	port->peer = SSL_get_peer_certificate(port->ssl);

	/* and extract the Common Name and Distinguished Name from it. */
	port->peer_cn = NULL;
	port->peer_dn = NULL;
	port->peer_cert_valid = false;
	if (port->peer != NULL)
	{
		int			len;
		const X509_NAME *x509name = X509_get_subject_name(port->peer);
		char	   *peer_dn;
		BIO		   *bio = NULL;
		BUF_MEM    *bio_buf = NULL;

		len = X509_NAME_get_text_by_NID(unconstify(X509_NAME *, x509name), NID_commonName, NULL, 0);
		if (len != -1)
		{
			char	   *peer_cn;

			peer_cn = MemoryContextAlloc(TopMemoryContext, len + 1);
			r = X509_NAME_get_text_by_NID(unconstify(X509_NAME *, x509name), NID_commonName, peer_cn,
										  len + 1);
			peer_cn[len] = '\0';
			if (r != len)
			{
				/* shouldn't happen */
				pfree(peer_cn);
				return -1;
			}

			/*
			 * Reject embedded NULLs in certificate common name to prevent
			 * attacks like CVE-2009-4034.
			 */
			if (len != strlen(peer_cn))
			{
				ereport(COMMERROR,
						(errcode(ERRCODE_PROTOCOL_VIOLATION),
						 errmsg("SSL certificate's common name contains embedded null")));
				pfree(peer_cn);
				return -1;
			}

			port->peer_cn = peer_cn;
		}

		bio = BIO_new(BIO_s_mem());
		if (!bio)
		{
			if (port->peer_cn != NULL)
			{
				pfree(port->peer_cn);
				port->peer_cn = NULL;
			}
			return -1;
		}

		/*
		 * RFC2253 is the closest thing to an accepted standard format for
		 * DNs. We have documented how to produce this format from a
		 * certificate. It uses commas instead of slashes for delimiters,
		 * which make regular expression matching a bit easier. Also note that
		 * it prints the Subject fields in reverse order.
		 */
		if (X509_NAME_print_ex(bio, x509name, 0, XN_FLAG_RFC2253) == -1 ||
			BIO_get_mem_ptr(bio, &bio_buf) <= 0)
		{
			BIO_free(bio);
			if (port->peer_cn != NULL)
			{
				pfree(port->peer_cn);
				port->peer_cn = NULL;
			}
			return -1;
		}
		peer_dn = MemoryContextAlloc(TopMemoryContext, bio_buf->length + 1);
		memcpy(peer_dn, bio_buf->data, bio_buf->length);
		len = bio_buf->length;
		BIO_free(bio);
		peer_dn[len] = '\0';
		if (len != strlen(peer_dn))
		{
			ereport(COMMERROR,
					(errcode(ERRCODE_PROTOCOL_VIOLATION),
					 errmsg("SSL certificate's distinguished name contains embedded null")));
			pfree(peer_dn);
			if (port->peer_cn != NULL)
			{
				pfree(port->peer_cn);
				port->peer_cn = NULL;
			}
			return -1;
		}

		port->peer_dn = peer_dn;

		port->peer_cert_valid = true;
	}

	return 0;
}

void
be_tls_close(Port *port)
{
	if (port->ssl)
	{
		SSL_shutdown(port->ssl);
		SSL_free(port->ssl);
		port->ssl = NULL;
		port->ssl_in_use = false;
	}

	if (port->peer)
	{
		X509_free(port->peer);
		port->peer = NULL;
	}

	if (port->peer_cn)
	{
		pfree(port->peer_cn);
		port->peer_cn = NULL;
	}

	if (port->peer_dn)
	{
		pfree(port->peer_dn);
		port->peer_dn = NULL;
	}
}

ssize_t
be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
{
	ssize_t		n;
	int			err;
	unsigned long ecode;

	errno = 0;
	ERR_clear_error();
	n = SSL_read(port->ssl, ptr, len);
	err = SSL_get_error(port->ssl, n);
	ecode = (err != SSL_ERROR_NONE || n < 0) ? ERR_get_error() : 0;
	switch (err)
	{
		case SSL_ERROR_NONE:
			/* a-ok */
			break;
		case SSL_ERROR_WANT_READ:
			*waitfor = WL_SOCKET_READABLE;
			errno = EWOULDBLOCK;
			n = -1;
			break;
		case SSL_ERROR_WANT_WRITE:
			*waitfor = WL_SOCKET_WRITEABLE;
			errno = EWOULDBLOCK;
			n = -1;
			break;
		case SSL_ERROR_SYSCALL:
			/* leave it to caller to ereport the value of errno */
			if (n != -1 || errno == 0)
			{
				errno = ECONNRESET;
				n = -1;
			}
			break;
		case SSL_ERROR_SSL:
			ereport(COMMERROR,
					(errcode(ERRCODE_PROTOCOL_VIOLATION),
					 errmsg("SSL error: %s", SSLerrmessage(ecode))));
			errno = ECONNRESET;
			n = -1;
			break;
		case SSL_ERROR_ZERO_RETURN:
			/* connection was cleanly shut down by peer */
			n = 0;
			break;
		default:
			ereport(COMMERROR,
					(errcode(ERRCODE_PROTOCOL_VIOLATION),
					 errmsg("unrecognized SSL error code: %d",
							err)));
			errno = ECONNRESET;
			n = -1;
			break;
	}

	return n;
}

ssize_t
be_tls_write(Port *port, const void *ptr, size_t len, int *waitfor)
{
	ssize_t		n;
	int			err;
	unsigned long ecode;

	errno = 0;
	ERR_clear_error();
	n = SSL_write(port->ssl, ptr, len);
	err = SSL_get_error(port->ssl, n);
	ecode = (err != SSL_ERROR_NONE || n < 0) ? ERR_get_error() : 0;
	switch (err)
	{
		case SSL_ERROR_NONE:
			/* a-ok */
			break;
		case SSL_ERROR_WANT_READ:
			*waitfor = WL_SOCKET_READABLE;
			errno = EWOULDBLOCK;
			n = -1;
			break;
		case SSL_ERROR_WANT_WRITE:
			*waitfor = WL_SOCKET_WRITEABLE;
			errno = EWOULDBLOCK;
			n = -1;
			break;
		case SSL_ERROR_SYSCALL:

			/*
			 * Leave it to caller to ereport the value of errno.  However, if
			 * errno is still zero then assume it's a read EOF situation, and
			 * report ECONNRESET.  (This seems possible because SSL_write can
			 * also do reads.)
			 */
			if (n != -1 || errno == 0)
			{
				errno = ECONNRESET;
				n = -1;
			}
			break;
		case SSL_ERROR_SSL:
			ereport(COMMERROR,
					(errcode(ERRCODE_PROTOCOL_VIOLATION),
					 errmsg("SSL error: %s", SSLerrmessage(ecode))));
			errno = ECONNRESET;
			n = -1;
			break;
		case SSL_ERROR_ZERO_RETURN:

			/*
			 * the SSL connection was closed, leave it to the caller to
			 * ereport it
			 */
			errno = ECONNRESET;
			n = -1;
			break;
		default:
			ereport(COMMERROR,
					(errcode(ERRCODE_PROTOCOL_VIOLATION),
					 errmsg("unrecognized SSL error code: %d",
							err)));
			errno = ECONNRESET;
			n = -1;
			break;
	}

	return n;
}

/* ------------------------------------------------------------ */
/*						Internal functions						*/
/* ------------------------------------------------------------ */

/*
 * Private substitute BIO: this does the sending and receiving using send() and
 * recv() instead. This is so that we can enable and disable interrupts
 * just while calling recv(). We cannot have interrupts occurring while
 * the bulk of OpenSSL runs, because it uses malloc() and possibly other
 * non-reentrant libc facilities. We also need to call send() and recv()
 * directly so it gets passed through the socket/signals layer on Win32.
 *
 * These functions are closely modelled on the standard socket BIO in OpenSSL;
 * see sock_read() and sock_write() in OpenSSL's crypto/bio/bss_sock.c.
 */

static BIO_METHOD *port_bio_method_ptr = NULL;

static int
port_bio_read(BIO *h, char *buf, int size)
{
	int			res = 0;
	Port	   *port = (Port *) BIO_get_data(h);

	if (buf != NULL)
	{
		res = secure_raw_read(port, buf, size);
		BIO_clear_retry_flags(h);
		port->last_read_was_eof = res == 0;
		if (res <= 0)
		{
			/* If we were interrupted, tell caller to retry */
			if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)
			{
				BIO_set_retry_read(h);
			}
		}
	}

	return res;
}

static int
port_bio_write(BIO *h, const char *buf, int size)
{
	int			res = 0;

	res = secure_raw_write(((Port *) BIO_get_data(h)), buf, size);
	BIO_clear_retry_flags(h);
	if (res <= 0)
	{
		/* If we were interrupted, tell caller to retry */
		if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)
		{
			BIO_set_retry_write(h);
		}
	}

	return res;
}

static long
port_bio_ctrl(BIO *h, int cmd, long num, void *ptr)
{
	long		res;
	Port	   *port = (Port *) BIO_get_data(h);

	switch (cmd)
	{
		case BIO_CTRL_EOF:

			/*
			 * This should not be needed. port_bio_read already has a way to
			 * signal EOF to OpenSSL. However, OpenSSL made an undocumented,
			 * backwards-incompatible change and now expects EOF via BIO_ctrl.
			 * See https://github.com/openssl/openssl/issues/8208
			 */
			res = port->last_read_was_eof;
			break;
		case BIO_CTRL_FLUSH:
			/* libssl expects all BIOs to support BIO_flush. */
			res = 1;
			break;
		default:
			res = 0;
			break;
	}

	return res;
}

static BIO_METHOD *
port_bio_method(void)
{
	if (!port_bio_method_ptr)
	{
		int			my_bio_index;

		my_bio_index = BIO_get_new_index();
		if (my_bio_index == -1)
			return NULL;
		my_bio_index |= BIO_TYPE_SOURCE_SINK;
		port_bio_method_ptr = BIO_meth_new(my_bio_index, "PostgreSQL backend socket");
		if (!port_bio_method_ptr)
			return NULL;
		if (!BIO_meth_set_write(port_bio_method_ptr, port_bio_write) ||
			!BIO_meth_set_read(port_bio_method_ptr, port_bio_read) ||
			!BIO_meth_set_ctrl(port_bio_method_ptr, port_bio_ctrl))
		{
			BIO_meth_free(port_bio_method_ptr);
			port_bio_method_ptr = NULL;
			return NULL;
		}
	}
	return port_bio_method_ptr;
}

static int
ssl_set_port_bio(Port *port)
{
	BIO		   *bio;
	BIO_METHOD *bio_method;

	bio_method = port_bio_method();
	if (bio_method == NULL)
		return 0;

	bio = BIO_new(bio_method);
	if (bio == NULL)
		return 0;

	BIO_set_data(bio, port);
	BIO_set_init(bio, 1);

	SSL_set_bio(port->ssl, bio, bio);
	return 1;
}

/*
 *	Load precomputed DH parameters.
 *
 *	To prevent "downgrade" attacks, we perform a number of checks
 *	to verify that the DBA-generated DH parameters file contains
 *	what we expect it to contain.
 */
static DH  *
load_dh_file(char *filename, bool isServerStart)
{
	FILE	   *fp;
	DH		   *dh = NULL;
	int			codes;

	/* attempt to open file.  It's not an error if it doesn't exist. */
	if ((fp = AllocateFile(filename, "r")) == NULL)
	{
		ereport(isServerStart ? FATAL : LOG,
				(errcode_for_file_access(),
				 errmsg("could not open DH parameters file \"%s\": %m",
						filename)));
		return NULL;
	}

	dh = PEM_read_DHparams(fp, NULL, NULL, NULL);
	FreeFile(fp);

	if (dh == NULL)
	{
		ereport(isServerStart ? FATAL : LOG,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("could not load DH parameters file: %s",
						SSLerrmessage(ERR_get_error()))));
		return NULL;
	}

	/* make sure the DH parameters are usable */
	if (DH_check(dh, &codes) == 0)
	{
		ereport(isServerStart ? FATAL : LOG,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("invalid DH parameters: %s",
						SSLerrmessage(ERR_get_error()))));
		DH_free(dh);
		return NULL;
	}
	if (codes & DH_CHECK_P_NOT_PRIME)
	{
		ereport(isServerStart ? FATAL : LOG,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("invalid DH parameters: p is not prime")));
		DH_free(dh);
		return NULL;
	}
	if ((codes & DH_NOT_SUITABLE_GENERATOR) &&
		(codes & DH_CHECK_P_NOT_SAFE_PRIME))
	{
		ereport(isServerStart ? FATAL : LOG,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("invalid DH parameters: neither suitable generator or safe prime")));
		DH_free(dh);
		return NULL;
	}

	return dh;
}

/*
 *	Load hardcoded DH parameters.
 *
 *	If DH parameters cannot be loaded from a specified file, we can load
 *	the hardcoded DH parameters supplied with the backend to prevent
 *	problems.
 */
static DH  *
load_dh_buffer(const char *buffer, size_t len)
{
	BIO		   *bio;
	DH		   *dh = NULL;

	bio = BIO_new_mem_buf(buffer, len);
	if (bio == NULL)
		return NULL;
	dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
	if (dh == NULL)
		ereport(DEBUG2,
				(errmsg_internal("DH load buffer: %s",
								 SSLerrmessage(ERR_get_error()))));
	BIO_free(bio);

	return dh;
}

/*
 *	Passphrase collection callback using ssl_passphrase_command
 */
static int
ssl_external_passwd_cb(char *buf, int size, int rwflag, void *userdata)
{
	/* same prompt as OpenSSL uses internally */
	const char *prompt = "Enter PEM pass phrase:";
	const char *cmd = userdata;

	Assert(rwflag == 0);

	return run_ssl_passphrase_command(cmd, prompt, ssl_is_server_start, buf, size);
}

/*
 * Dummy passphrase callback
 *
 * If OpenSSL is told to use a passphrase-protected server key, by default
 * it will issue a prompt on /dev/tty and try to read a key from there.
 * That's no good during a postmaster SIGHUP cycle, not to mention SSL context
 * reload in an EXEC_BACKEND postmaster child.  So override it with this dummy
 * function that just returns an empty passphrase, guaranteeing failure.
 */
static int
dummy_ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata)
{
	/* Set flag to change the error message we'll report */
	dummy_ssl_passwd_cb_called = true;
	/* And return empty string */
	Assert(size > 0);
	buf[0] = '\0';
	return 0;
}

/*
 * Examines the provided certificate name, and if it's too long to log or
 * contains unprintable ASCII, escapes and truncates it. The return value is
 * always a new palloc'd string. (The input string is still modified in place,
 * for ease of implementation.)
 */
static char *
prepare_cert_name(char *name)
{
	size_t		namelen = strlen(name);
	char	   *truncated = name;

	/*
	 * Common Names are 64 chars max, so for a common case where the CN is the
	 * last field, we can still print the longest possible CN with a
	 * 7-character prefix (".../CN=[64 chars]"), for a reasonable limit of 71
	 * characters.
	 */
#define MAXLEN 71

	if (namelen > MAXLEN)
	{
		/*
		 * Keep the end of the name, not the beginning, since the most
		 * specific field is likely to give users the most information.
		 */
		truncated = name + namelen - MAXLEN;
		truncated[0] = truncated[1] = truncated[2] = '.';
		namelen = MAXLEN;
	}

#undef MAXLEN

	return pg_clean_ascii(truncated, 0);
}

/*
 *	Certificate verification callback
 *
 *	This callback allows us to examine intermediate problems during
 *	verification, for later logging.
 *
 *	This callback also allows us to override the default acceptance
 *	criteria (e.g., accepting self-signed or expired certs), but
 *	for now we accept the default checks.
 */
static int
verify_cb(int ok, X509_STORE_CTX *ctx)
{
	int			depth;
	int			errcode;
	const char *errstring;
	StringInfoData str;
	X509	   *cert;
	SSL		   *ssl;
	struct CallbackErr *cb_err;

	if (ok)
	{
		/* Nothing to do for the successful case. */
		return ok;
	}

	/* Pull all the information we have on the verification failure. */
	depth = X509_STORE_CTX_get_error_depth(ctx);
	errcode = X509_STORE_CTX_get_error(ctx);
	errstring = X509_verify_cert_error_string(errcode);

	/*
	 * Extract the current SSL and CallbackErr object to use for passing error
	 * detail back from the callback.
	 */
	ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
	cb_err = (struct CallbackErr *) SSL_get_ex_data(ssl, 0);

	initStringInfo(&str);
	appendStringInfo(&str,
					 _("Client certificate verification failed at depth %d: %s."),
					 depth, errstring);

	cert = X509_STORE_CTX_get_current_cert(ctx);
	if (cert)
	{
		char	   *subject,
				   *issuer;
		char	   *sub_prepared,
				   *iss_prepared;
		char	   *serialno;
		ASN1_INTEGER *sn;
		BIGNUM	   *b;

		/*
		 * Get the Subject and Issuer for logging, but don't let maliciously
		 * huge certs flood the logs, and don't reflect non-ASCII bytes into
		 * it either.
		 */
		subject = X509_NAME_to_cstring(X509_get_subject_name(cert));
		sub_prepared = prepare_cert_name(subject);
		pfree(subject);

		issuer = X509_NAME_to_cstring(X509_get_issuer_name(cert));
		iss_prepared = prepare_cert_name(issuer);
		pfree(issuer);

		/*
		 * Pull the serial number, too, in case a Subject is still ambiguous.
		 * This mirrors be_tls_get_peer_serial().
		 */
		sn = X509_get_serialNumber(cert);
		b = ASN1_INTEGER_to_BN(sn, NULL);
		serialno = BN_bn2dec(b);

		appendStringInfoChar(&str, '\n');
		appendStringInfo(&str,
						 _("Failed certificate data (unverified): subject \"%s\", serial number %s, issuer \"%s\"."),
						 sub_prepared, serialno ? serialno : _("unknown"),
						 iss_prepared);

		BN_free(b);
		OPENSSL_free(serialno);
		pfree(iss_prepared);
		pfree(sub_prepared);
	}

	/* Store our detail message to be logged later. */
	cb_err->cert_errdetail = str.data;

	return ok;
}

/*
 *	This callback is used to copy SSL information messages
 *	into the PostgreSQL log.
 */
static void
info_cb(const SSL *ssl, int type, int args)
{
	const char *desc;

	desc = SSL_state_string_long(ssl);

	switch (type)
	{
		case SSL_CB_HANDSHAKE_START:
			ereport(DEBUG4,
					(errmsg_internal("SSL: handshake start: \"%s\"", desc)));
			break;
		case SSL_CB_HANDSHAKE_DONE:
			ereport(DEBUG4,
					(errmsg_internal("SSL: handshake done: \"%s\"", desc)));
			break;
		case SSL_CB_ACCEPT_LOOP:
			ereport(DEBUG4,
					(errmsg_internal("SSL: accept loop: \"%s\"", desc)));
			break;
		case SSL_CB_ACCEPT_EXIT:
			ereport(DEBUG4,
					(errmsg_internal("SSL: accept exit (%d): \"%s\"", args, desc)));
			break;
		case SSL_CB_CONNECT_LOOP:
			ereport(DEBUG4,
					(errmsg_internal("SSL: connect loop: \"%s\"", desc)));
			break;
		case SSL_CB_CONNECT_EXIT:
			ereport(DEBUG4,
					(errmsg_internal("SSL: connect exit (%d): \"%s\"", args, desc)));
			break;
		case SSL_CB_READ_ALERT:
			ereport(DEBUG4,
					(errmsg_internal("SSL: read alert (0x%04x): \"%s\"", args, desc)));
			break;
		case SSL_CB_WRITE_ALERT:
			ereport(DEBUG4,
					(errmsg_internal("SSL: write alert (0x%04x): \"%s\"", args, desc)));
			break;
	}
}

/* See pqcomm.h comments on OpenSSL implementation of ALPN (RFC 7301) */
static const unsigned char alpn_protos[] = PG_ALPN_PROTOCOL_VECTOR;

/*
 * Server callback for ALPN negotiation. We use the standard "helper" function
 * even though currently we only accept one value.
 */
static int
alpn_cb(SSL *ssl,
		const unsigned char **out,
		unsigned char *outlen,
		const unsigned char *in,
		unsigned int inlen,
		void *userdata)
{
	/*
	 * Why does OpenSSL provide a helper function that requires a nonconst
	 * vector when the callback is declared to take a const vector? What are
	 * we to do with that?
	 */
	int			retval;

	Assert(userdata != NULL);
	Assert(out != NULL);
	Assert(outlen != NULL);
	Assert(in != NULL);

	retval = SSL_select_next_proto((unsigned char **) out, outlen,
								   alpn_protos, sizeof(alpn_protos),
								   in, inlen);
	if (*out == NULL || *outlen > sizeof(alpn_protos) || *outlen <= 0)
		return SSL_TLSEXT_ERR_NOACK;	/* can't happen */

	if (retval == OPENSSL_NPN_NEGOTIATED)
		return SSL_TLSEXT_ERR_OK;
	else
	{
		/*
		 * The client doesn't support our protocol.  Reject the connection
		 * with TLS "no_application_protocol" alert, per RFC 7301.
		 */
		return SSL_TLSEXT_ERR_ALERT_FATAL;
	}
}

#ifdef HAVE_SSL_CTX_SET_CLIENT_HELLO_CB
/*
 * ssl_update_ssl
 *
 * Replace certificate/key and CA in an SSL object to match the, via the SNI
 * extension, selected host configuration for the connection.  The SSL_CTX
 * object to use should be passed in as ctx.  This function will update the
 * SSL object in-place.
 */
static bool
ssl_update_ssl(SSL *ssl, HostsLine *host_config)
{
	SSL_CTX    *ctx = host_config->ssl_ctx;

	X509	   *cert;
	EVP_PKEY   *key;

	STACK_OF(X509) * chain;

	Assert(ctx != NULL);
	/*-
	 * Make use of the already-loaded certificate chain and key. At first
	 * glance, SSL_set_SSL_CTX() looks like the easiest way to do this, but
	 * beware -- it has very odd behavior:
	 *
	 *     https://github.com/openssl/openssl/issues/6109
	 */
	cert = SSL_CTX_get0_certificate(ctx);
	key = SSL_CTX_get0_privatekey(ctx);

	Assert(cert && key);

	if (!SSL_CTX_get0_chain_certs(ctx, &chain)
		|| !SSL_use_cert_and_key(ssl, cert, key, chain, 1 /* override */ )
		|| !SSL_check_private_key(ssl))
	{
		/*
		 * This shouldn't really be possible, since the inputs came from a
		 * SSL_CTX that was already populated by OpenSSL.
		 */
		ereport(COMMERROR,
				errcode(ERRCODE_INTERNAL_ERROR),
				errmsg_internal("could not update certificate chain: %s",
								SSLerrmessage(ERR_get_error())));
		return false;
	}

	if (host_config->ssl_ca && host_config->ssl_ca[0])
	{
		/*
		 * Copy the trust store and list of roots over from the SSL_CTX.
		 */
		X509_STORE *ca_store = SSL_CTX_get_cert_store(ctx);

		STACK_OF(X509_NAME) * roots;

		/*
		 * The trust store appears to be the only setting that this function
		 * can't override via the (SSL *) pointer directly. Instead, share it
		 * with the active SSL_CTX (this should always be SSL_context).
		 */
		Assert(SSL_context == SSL_get_SSL_CTX(ssl));
		SSL_CTX_set1_cert_store(SSL_context, ca_store);

		/*
		 * SSL_set_client_CA_list() will take ownership of its argument, so we
		 * need to duplicate it.
		 */
		if ((roots = SSL_CTX_get_client_CA_list(ctx)) == NULL
			|| (roots = SSL_dup_CA_list(roots)) == NULL)
		{
			ereport(COMMERROR,
					errcode(ERRCODE_INTERNAL_ERROR),
					errmsg_internal("could not duplicate SSL_CTX CA list: %s",
									SSLerrmessage(ERR_get_error())));
			return false;
		}

		SSL_set_client_CA_list(ssl, roots);

		/*
		 * Always ask for SSL client cert, but don't fail if it's not
		 * presented.  We might fail such connections later, depending on what
		 * we find in pg_hba.conf.
		 */
		SSL_set_verify(ssl,
					   (SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE),
					   verify_cb);

		ssl_loaded_verify_locations = true;
	}

	return true;
}

/*
 * sni_clienthello_cb
 *
 * Callback for extracting the servername extension from the TLS handshake
 * during ClientHello.  There is a callback in OpenSSL for the servername
 * specifically but OpenSSL themselves advice against using it as it is more
 * dependent on ordering for execution.
 */
static int
sni_clienthello_cb(SSL *ssl, int *al, void *arg)
{
	const char *tlsext_hostname;
	const unsigned char *tlsext;
	size_t		left,
				len;
	HostsLine  *install_config = NULL;

	if (!ssl_sni)
	{
		install_config = SSL_hosts->default_host;
		goto found;
	}

	if (SSL_client_hello_get0_ext(ssl, TLSEXT_TYPE_server_name, &tlsext, &left))
	{
		if (left <= 2)
		{
			*al = SSL_AD_DECODE_ERROR;
			return 0;
		}
		len = (*(tlsext++) << 8);
		len += *(tlsext)++;
		if (len + 2 != left)
		{
			*al = SSL_AD_DECODE_ERROR;
			return 0;
		}

		left = len;

		if (left == 0 || *tlsext++ != TLSEXT_NAMETYPE_host_name)
		{
			*al = SSL_AD_DECODE_ERROR;
			return 0;
		}

		left--;

		/*
		 * Now we can finally pull out the byte array with the actual
		 * hostname.
		 */
		if (left <= 2)
		{
			*al = SSL_AD_DECODE_ERROR;
			return 0;
		}
		len = (*(tlsext++) << 8);
		len += *(tlsext++);
		if (len + 2 > left)
		{
			*al = SSL_AD_DECODE_ERROR;
			return 0;
		}
		left = len;
		tlsext_hostname = (const char *) tlsext;

		/*
		 * We have a requested hostname from the client, match against all
		 * entries in the pg_hosts configuration and attempt to find a match.
		 * Matching is done case insensitive as per RFC 952 and RFC 921.
		 */
		foreach_ptr(HostsLine, host, SSL_hosts->sni)
		{
			foreach_ptr(char, hostname, host->hostnames)
			{
				if (strlen(hostname) == len &&
					pg_strncasecmp(hostname, tlsext_hostname, len) == 0)
				{
					install_config = host;
					goto found;
				}
			}
		}

		/*
		 * If no host specific match was found, and there is a default config,
		 * then fall back to using that.
		 */
		if (!install_config && SSL_hosts->default_host)
			install_config = SSL_hosts->default_host;
	}

	/*
	 * No hostname TLS extension in the handshake, use the default or no_sni
	 * configurations if available.
	 */
	else
	{
		tlsext_hostname = NULL;

		if (SSL_hosts->no_sni)
			install_config = SSL_hosts->no_sni;
		else if (SSL_hosts->default_host)
			install_config = SSL_hosts->default_host;
		else
		{
			/*
			 * Reaching here means that we didn't get a hostname in the TLS
			 * extension and the server has been configured to not allow any
			 * connections without a specified hostname.
			 *
			 * The error message for a missing server_name should, according
			 * to RFC 8446, be missing_extension. This isn't entirely ideal
			 * since the user won't be able to tell which extension the server
			 * considered missing.  Sending unrecognized_name would be a more
			 * helpful error, but for now we stick to the RFC.
			 */
			*al = SSL_AD_MISSING_EXTENSION;

			ereport(COMMERROR,
					(errcode(ERRCODE_PROTOCOL_VIOLATION),
					 errmsg("no hostname provided in callback, and no fallback configured")));
			return SSL_CLIENT_HELLO_ERROR;
		}
	}

	/*
	 * If we reach here without a context chosen as the session context then
	 * fail the handshake and terminate the connection.
	 */
	if (install_config == NULL)
	{
		if (tlsext_hostname)
			*al = SSL_AD_UNRECOGNIZED_NAME;
		else
			*al = SSL_AD_MISSING_EXTENSION;
		return SSL_CLIENT_HELLO_ERROR;
	}

found:
	if (!ssl_update_ssl(ssl, install_config))
	{
		*al = SSL_AD_INTERNAL_ERROR;
		ereport(COMMERROR,
				errcode(ERRCODE_PROTOCOL_VIOLATION),
				errmsg("failed to switch to SSL configuration for host, terminating connection"));
		return SSL_CLIENT_HELLO_ERROR;
	}

	return SSL_CLIENT_HELLO_SUCCESS;
}
#endif							/* HAVE_SSL_CTX_SET_CLIENT_HELLO_CB */

/*
 * Set DH parameters for generating ephemeral DH keys.  The
 * DH parameters can take a long time to compute, so they must be
 * precomputed.
 *
 * Since few sites will bother to create a parameter file, we also
 * provide a fallback to the parameters provided by the OpenSSL
 * project.
 *
 * These values can be static (once loaded or computed) since the
 * OpenSSL library can efficiently generate random keys from the
 * information provided.
 */
static bool
initialize_dh(SSL_CTX *context, bool isServerStart)
{
	DH		   *dh = NULL;

	SSL_CTX_set_options(context, SSL_OP_SINGLE_DH_USE);

	if (ssl_dh_params_file[0])
		dh = load_dh_file(ssl_dh_params_file, isServerStart);
	if (!dh)
		dh = load_dh_buffer(FILE_DH2048, sizeof(FILE_DH2048));
	if (!dh)
	{
		ereport(isServerStart ? FATAL : LOG,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("DH: could not load DH parameters")));
		return false;
	}

	if (SSL_CTX_set_tmp_dh(context, dh) != 1)
	{
		ereport(isServerStart ? FATAL : LOG,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("DH: could not set DH parameters: %s",
						SSLerrmessage(ERR_get_error()))));
		DH_free(dh);
		return false;
	}

	DH_free(dh);
	return true;
}

/*
 * Set ECDH parameters for generating ephemeral Elliptic Curve DH
 * keys.  This is much simpler than the DH parameters, as we just
 * need to provide the name of the curve to OpenSSL.
 */
static bool
initialize_ecdh(SSL_CTX *context, bool isServerStart)
{
	if (SSL_CTX_set1_groups_list(context, SSLECDHCurve) != 1)
	{
		/*
		 * OpenSSL 3.3.0 introduced proper error messages for group parsing
		 * errors, earlier versions returns "no SSL error reported" which is
		 * far from helpful. For older versions, we replace with a better
		 * error message. Injecting the error into the OpenSSL error queue
		 * need APIs from OpenSSL 3.0.
		 */
		ereport(isServerStart ? FATAL : LOG,
				errcode(ERRCODE_CONFIG_FILE_ERROR),
				errmsg("could not set group names specified in ssl_groups: %s",
					   SSLerrmessageExt(ERR_get_error(),
										_("No valid groups found"))),
				errhint("Ensure that each group name is spelled correctly and supported by the installed version of OpenSSL."));
		return false;
	}

	return true;
}

/*
 * Obtain reason string for passed SSL errcode with replacement
 *
 * The error message supplied in replacement will be used in case the error
 * code from OpenSSL is 0, else the error message from SSLerrmessage() will
 * be returned.
 *
 * Not all versions of OpenSSL place an error on the queue even for failing
 * operations, which will yield "no SSL error reported" by SSLerrmessage. This
 * function can be used to ensure that a proper error message is displayed for
 * versions reporting no error, while using the OpenSSL error via SSLerrmessage
 * for versions where there is one.
 */
static const char *
SSLerrmessageExt(unsigned long ecode, const char *replacement)
{
	if (ecode == 0)
		return replacement;
	else
		return SSLerrmessage(ecode);
}

/*
 * Obtain reason string for passed SSL errcode
 *
 * ERR_get_error() is used by caller to get errcode to pass here.
 *
 * Some caution is needed here since ERR_reason_error_string will return NULL
 * if it doesn't recognize the error code, or (in OpenSSL >= 3) if the code
 * represents a system errno value.  We don't want to return NULL ever.
 */
static const char *
SSLerrmessage(unsigned long ecode)
{
	const char *errreason;
	static char errbuf[36];

	if (ecode == 0)
		return _("no SSL error reported");
	errreason = ERR_reason_error_string(ecode);
	if (errreason != NULL)
		return errreason;

	/*
	 * In OpenSSL 3.0.0 and later, ERR_reason_error_string does not map system
	 * errno values anymore.  (See OpenSSL source code for the explanation.)
	 * We can cover that shortcoming with this bit of code.  Older OpenSSL
	 * versions don't have the ERR_SYSTEM_ERROR macro, but that's okay because
	 * they don't have the shortcoming either.
	 */
#ifdef ERR_SYSTEM_ERROR
	if (ERR_SYSTEM_ERROR(ecode))
		return strerror(ERR_GET_REASON(ecode));
#endif

	/* No choice but to report the numeric ecode */
	snprintf(errbuf, sizeof(errbuf), _("SSL error code %lu"), ecode);
	return errbuf;
}

int
be_tls_get_cipher_bits(Port *port)
{
	int			bits;

	if (port->ssl)
	{
		SSL_get_cipher_bits(port->ssl, &bits);
		return bits;
	}
	else
		return 0;
}

const char *
be_tls_get_version(Port *port)
{
	if (port->ssl)
		return SSL_get_version(port->ssl);
	else
		return NULL;
}

const char *
be_tls_get_cipher(Port *port)
{
	if (port->ssl)
		return SSL_get_cipher(port->ssl);
	else
		return NULL;
}

void
be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
{
	if (port->peer)
		strlcpy(ptr, X509_NAME_to_cstring(X509_get_subject_name(port->peer)), len);
	else
		ptr[0] = '\0';
}

void
be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
{
	if (port->peer)
		strlcpy(ptr, X509_NAME_to_cstring(X509_get_issuer_name(port->peer)), len);
	else
		ptr[0] = '\0';
}

void
be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
{
	if (port->peer)
	{
		ASN1_INTEGER *serial;
		BIGNUM	   *b;
		char	   *decimal;

		serial = X509_get_serialNumber(port->peer);
		b = ASN1_INTEGER_to_BN(serial, NULL);
		decimal = BN_bn2dec(b);

		BN_free(b);
		strlcpy(ptr, decimal, len);
		OPENSSL_free(decimal);
	}
	else
		ptr[0] = '\0';
}

char *
be_tls_get_certificate_hash(Port *port, size_t *len)
{
	X509	   *server_cert;
	char	   *cert_hash;
	const EVP_MD *algo_type = NULL;
	unsigned char hash[EVP_MAX_MD_SIZE];	/* size for SHA-512 */
	unsigned int hash_size;
	int			algo_nid;

	*len = 0;
	server_cert = SSL_get_certificate(port->ssl);
	if (server_cert == NULL)
		return NULL;

	/*
	 * Get the signature algorithm of the certificate to determine the hash
	 * algorithm to use for the result.  Prefer X509_get_signature_info(),
	 * introduced in OpenSSL 1.1.1, which can handle RSA-PSS signatures.
	 */
#if HAVE_X509_GET_SIGNATURE_INFO
	if (!X509_get_signature_info(server_cert, &algo_nid, NULL, NULL, NULL))
#else
	if (!OBJ_find_sigid_algs(X509_get_signature_nid(server_cert),
							 &algo_nid, NULL))
#endif
		elog(ERROR, "could not determine server certificate signature algorithm");

	/*
	 * The TLS server's certificate bytes need to be hashed with SHA-256 if
	 * its signature algorithm is MD5 or SHA-1 as per RFC 5929
	 * (https://tools.ietf.org/html/rfc5929#section-4.1).  If something else
	 * is used, the same hash as the signature algorithm is used.
	 */
	switch (algo_nid)
	{
		case NID_md5:
		case NID_sha1:
			algo_type = EVP_sha256();
			break;
		default:
			algo_type = EVP_get_digestbynid(algo_nid);
			if (algo_type == NULL)
				elog(ERROR, "could not find digest for NID %s",
					 OBJ_nid2sn(algo_nid));
			break;
	}

	/* generate and save the certificate hash */
	if (!X509_digest(server_cert, algo_type, hash, &hash_size))
		elog(ERROR, "could not generate server certificate hash");

	cert_hash = palloc(hash_size);
	memcpy(cert_hash, hash, hash_size);
	*len = hash_size;

	return cert_hash;
}

/*
 * Convert an X509 subject name to a cstring.
 *
 */
static char *
X509_NAME_to_cstring(const X509_NAME *name)
{
	BIO		   *membuf = BIO_new(BIO_s_mem());
	int			i,
				nid,
				count = X509_NAME_entry_count(name);
	const X509_NAME_ENTRY *e;
	const ASN1_STRING *v;
	const char *field_name;
	size_t		size;
	char		nullterm;
	char	   *sp;
	char	   *dp;
	char	   *result;

	if (membuf == NULL)
		ereport(ERROR,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("could not create BIO")));

	(void) BIO_set_close(membuf, BIO_CLOSE);
	for (i = 0; i < count; i++)
	{
		e = X509_NAME_get_entry(name, i);
		nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e));
		if (nid == NID_undef)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("could not get NID for ASN1_OBJECT object")));
		v = X509_NAME_ENTRY_get_data(e);
		field_name = OBJ_nid2sn(nid);
		if (field_name == NULL)
			field_name = OBJ_nid2ln(nid);
		if (field_name == NULL)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid)));
		BIO_printf(membuf, "/%s=", field_name);
		ASN1_STRING_print_ex(membuf, v,
							 ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
							  | ASN1_STRFLGS_UTF8_CONVERT));
	}

	/* ensure null termination of the BIO's content */
	nullterm = '\0';
	BIO_write(membuf, &nullterm, 1);
	size = BIO_get_mem_data(membuf, &sp);
	dp = pg_any_to_server(sp, size - 1, PG_UTF8);

	result = pstrdup(dp);
	if (dp != sp)
		pfree(dp);
	if (BIO_free(membuf) != 1)
		elog(ERROR, "could not free OpenSSL BIO structure");

	return result;
}

/*
 * Convert TLS protocol version GUC enum to OpenSSL values
 *
 * This is a straightforward one-to-one mapping, but doing it this way makes
 * the definitions of ssl_min_protocol_version and ssl_max_protocol_version
 * independent of OpenSSL availability and version.
 *
 * If a version is passed that is not supported by the current OpenSSL
 * version, then we return -1.  If a nonnegative value is returned,
 * subsequent code can assume it's working with a supported version.
 *
 * Note: this is rather similar to libpq's routine in fe-secure-openssl.c,
 * so make sure to update both routines if changing this one.
 */
static int
ssl_protocol_version_to_openssl(int v)
{
	switch (v)
	{
		case PG_TLS_ANY:
			return 0;
		case PG_TLS1_VERSION:
			return TLS1_VERSION;
		case PG_TLS1_1_VERSION:
#ifdef TLS1_1_VERSION
			return TLS1_1_VERSION;
#else
			break;
#endif
		case PG_TLS1_2_VERSION:
#ifdef TLS1_2_VERSION
			return TLS1_2_VERSION;
#else
			break;
#endif
		case PG_TLS1_3_VERSION:
#ifdef TLS1_3_VERSION
			return TLS1_3_VERSION;
#else
			break;
#endif
	}

	return -1;
}

/*
 * Likewise provide a mapping to strings.
 */
static const char *
ssl_protocol_version_to_string(int v)
{
	switch (v)
	{
		case PG_TLS_ANY:
			return "any";
		case PG_TLS1_VERSION:
			return "TLSv1";
		case PG_TLS1_1_VERSION:
			return "TLSv1.1";
		case PG_TLS1_2_VERSION:
			return "TLSv1.2";
		case PG_TLS1_3_VERSION:
			return "TLSv1.3";
	}

	return "(unrecognized)";
}

static uint32
host_cache_pointer(const char *key)
{
	uint32		hash;
	char	   *lkey = pstrdup(key);
	int			len = strlen(key);

	for (int i = 0; i < len; i++)
		lkey[i] = pg_tolower(lkey[i]);

	hash = string_hash((const void *) lkey, len);
	pfree(lkey);
	return hash;
}

static void
default_openssl_tls_init(SSL_CTX *context, bool isServerStart)
{
	if (isServerStart)
	{
		if (ssl_passphrase_command[0])
		{
			SSL_CTX_set_default_passwd_cb(context, ssl_external_passwd_cb);
			SSL_CTX_set_default_passwd_cb_userdata(context, ssl_passphrase_command);
		}
	}
	else
	{
		if (ssl_passphrase_command[0] && ssl_passphrase_command_supports_reload)
		{
			SSL_CTX_set_default_passwd_cb(context, ssl_external_passwd_cb);
			SSL_CTX_set_default_passwd_cb_userdata(context, ssl_passphrase_command);
		}
		else

			/*
			 * If reloading and no external command is configured, override
			 * OpenSSL's default handling of passphrase-protected files,
			 * because we don't want to prompt for a passphrase in an
			 * already-running server.
			 */
			SSL_CTX_set_default_passwd_cb(context, dummy_ssl_passwd_cb);
	}
}
