public inbox for [email protected]  
help / color / mirror / Atom feed
Re: Extension security improvement: Add support for extensions with an owned schema
2+ messages / 2 participants
[nested] [flat]

* Re: Extension security improvement: Add support for extensions with an owned schema
@ 2024-06-05 14:09  Marco Slot <[email protected]>
  0 siblings, 0 replies; 2+ messages in thread

From: Marco Slot @ 2024-06-05 14:09 UTC (permalink / raw)
  To: Jelte Fennema-Nio <[email protected]>; +Cc: pgsql-hackers

On Sun, Jun 2, 2024, 02:08 Jelte Fennema-Nio <[email protected]> wrote:
> This patch adds a new "owned_schema" option to the extension control
> file that can be set to true to indicate that this extension wants to
> own the schema in which it is installed.

Huge +1

Many managed PostgreSQL services block superuser access, but provide a
way for users to trigger a create/alter extension as superuser. There
have been various extensions whose SQL scripts can be tricked into
calling a function that was pre-created in the extension schema. This
is usually done by finding an unqualified call to a pg_catalog
function/operator, and overloading it with one whose arguments types
are a closer match for the provided values, which then takes
precedence regardless of search_path order. The custom function can
then do something like "alter user foo superuser".

The sequence of steps assumes the user already has some kind of admin
role and is gaining superuser access to their own database server.
However, the superuser implicitly has shell access, so it gives
attackers an additional set of tools to poke around in the managed
service. For instance, they can change the way the machine responds to
control plane requests, which can sometimes trigger further
escalations. In addition, many applications use the relatively
privileged default user, which means SQL injection issues can also
escalate into superuser access and beyond.

There are some static analysis tools like
https://github.com/timescale/pgspot that address this issue, though it
seems like a totally unnecessary hole. Using schema = pg_catalog,
relocatable = false, and doing an explicit create schema (without "if
not exists") plugs the hole by effectively disabling extension
schemas. For extensions I'm involved in, I consider this to be a hard
requirement.

I think Jelte's solution is preferable going forward, because it
preserves the flexibility that extension schemas were meant to
provide, and makes the potential hazards of reusing a schema more
explicit.

cheers,
Marco






^ permalink  raw  reply  [nested|flat] 2+ messages in thread

* LibreSSL and OpenSSL separation in libpq to support 1.1.1 deprecation
@ 2026-07-09 21:14  Daniel Gustafsson <[email protected]>
  0 siblings, 0 replies; 2+ messages in thread

From: Daniel Gustafsson @ 2026-07-09 21:14 UTC (permalink / raw)
  To: PostgreSQL Hackers <[email protected]>; +Cc: Jacob Champion <[email protected]>

Since the discussion on the minimum supported version of OpenSSL for v20 has
kicked off, I wanted to share what I believe is an important step on the path
to OpenSSL modernization; namely how to deal with LibreSSL.

Our LibreSSL support is currently based on the OpenSSL code and the OpenSSL
compatibility in LibreSSL, so much so that for quite some time we didn't even
document that it was supported even though it was buildfarm tested.  Over time
we've accumulated more ifdefs for differences in API support.  If we want to
modernize our OpenSSL support in libpq with v3+ API's once we've deprecated
1.1.1, the code will soon become unreadable and IMHO, a CVE hazard.

LibreSSL supports the 1.1.1 API, and they dont have the manpower to keep up
with the rapid pace of OpenSSL development so are likely to stay there for the
foreseeable future.  The libraries have already started diverging quite a lot
and it will continue (LibreSSL have also developed their own API which if I
were them is what I'd focus on).

As I can see it we have two choices wrt LibreSSL if we want to move past 1.1.1;
either deprecate support or separate it out such that OpenSSL and LibreSSL can
move at their own pace.  I don't like the idea of deprecating LibreSSL so I
favor the latter alternative.

The attached implements LibreSSL as a separate TLS library implementation (*)
in libpq with fe-secure-libressl.c and be-secure-libressl.c along with build
and test infrastructure.  This is a PoC rather than a polished for-inclusion
patch as of now, since I wanted to get the discussion going.  Some of the known
TODO items include:

    - Autoconf support is a POC and not tested at all yet, only the
      Meson side has been tested as of now
    - include/common/openssl.h needs to either be renamed, copied
      or at least have its comments extended
    - test/ssl/t/SSL/Backend/OpenSSL.pm has been hackily extended
      for now, the exact level of changes required here are not
      clear, and a future move to pytest may influence how much we
      want to invest here
    - the discovery in meson.build is likely rubbish and could be
      cleaned up by our Meson experts
    - there are likely lots of comments still that mention OpenSSL
      which could be expanded to say something about LibreSSL
    - documentation of the build option is missing
    - we can simplify a few uses of USE_OPENSSL || USE_LIBRESSL to
      just USE_SSL

There are no new features introduced (and none removed) or API usage changes,
the only changes are to cut away the ifdefs and only keep the code we actually
need per file.  This way the differences are quite easy to check by diffing
{fe|be}-secure-{openssl|libressl}.c.

While this duplicate a lot of code, it doesn't really add a maintenance burden
we don't already carry since we do support LibreSSL already, but it does alter
the burden for sure (not least when it comes to backpatching).  That being
said, staying on a long-since deprecated API level (we are at 1.0.1 with
warnings silenced) also doesn't sound great.

One option is to not do this until we have a case where we want to use a v3+
API and only do it then as a prerequisite step; keeping the patch rebased in
the back-pocket till that day comes.  Personally I have serverside SNI support
for LibreSSL on my radar for v20, which will need such a split in order to keep
the files readable as it will be implemented quite differently from its OpenSSL
counterpart.

All of the above is of course moot if we don't move the needle past 1.1.1 in
v20..

--
Daniel Gustafsson

(*) Yes.  The irony of me proposing to add a new TLS library implementation to
postgres is *not* lost on me.



Attachments:

  [application/octet-stream] vPoC-0001-ssl-Split-OpenSSL-and-LibreSSL-into-separate-mo.patch (142.7K, ../../[email protected]/2-vPoC-0001-ssl-Split-OpenSSL-and-LibreSSL-into-separate-mo.patch)
  download | inline diff:
From ae7b71271ee60896ab8a378866e7cddbbd46d00a Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <[email protected]>
Date: Thu, 9 Jul 2026 15:50:18 +0200
Subject: [PATCH vPoC] ssl: Split OpenSSL and LibreSSL into separate modules

LibreSSL support in PostgreSQL was added more as a consequence of the
OpenSSL compatibility of LibreSSL than a discussed library dependency.
Support for LibreSSL was in the beginning undocumented with no minimum
version specified.  The OpenSSL code in libpq handled the differences
with ifdefs.

As we are looking to deprecate support for OpenSSL 1.1.1 and modernize
our OpenSSL code with 3.0+ APIs, we need to separate support for the
libraries since LibreSSL still use the 1.1.1 style API.  If we keep the
support in the same file it will hurt readability with all the ifdefs
required, and risk hampering efforts to make use of modern OpenSSL API
features.  The recent support for serverside SNI is another example
where mixing the APIs caused problems, which ended up with serverside
SNI not being available when building with LibreSSL.

This copies the current OpenSSL support into mirrored LibreSSL files
and removes the compatibility fixes from both, with no new features
introduced.

There are still a few more things needed to take this patch from POC
status, some are listed below:

  TODO
    - Autoconf support is a POC and not tested yet
	- include/common/openssl.h needs to either be renamed, copied
	  or at least have its comments extended
	- test/ssl/t/SSL/Backend/OpenSSL.pm has been hackily extended
	  for now, the exact level of changes required here are not
	  clear, and a future move to pytest may influence how much we
	  want to invest here
---
 configure                                 |  112 +-
 configure.ac                              |   15 +-
 meson.build                               |   61 +-
 meson_options.txt                         |    4 +-
 src/backend/commands/variable.c           |    2 +-
 src/backend/libpq/Makefile                |    3 +
 src/backend/libpq/be-secure-libressl.c    | 1978 +++++++++++++++++++++
 src/backend/libpq/be-secure-openssl.c     |   97 +-
 src/backend/libpq/meson.build             |    6 +-
 src/include/common/openssl.h              |    4 +-
 src/include/libpq/libpq-be.h              |    8 +-
 src/include/libpq/libpq.h                 |    6 +-
 src/include/pg_config.h.in                |   20 +-
 src/include/pg_config_manual.h            |    2 +-
 src/interfaces/libpq/Makefile             |    4 +
 src/interfaces/libpq/fe-secure-libressl.c | 1905 ++++++++++++++++++++
 src/interfaces/libpq/fe-secure-openssl.c  |   22 +-
 src/interfaces/libpq/fe-secure.c          |    7 +-
 src/interfaces/libpq/libpq-int.h          |    8 +-
 src/interfaces/libpq/meson.build          |    6 +-
 src/port/pg_strong_random.c               |    4 +-
 src/test/ssl/t/001_ssltests.pl            |    4 +-
 src/test/ssl/t/002_scram.pl               |    4 +-
 src/test/ssl/t/003_sslinfo.pl             |    4 +-
 src/test/ssl/t/004_sni.pl                 |   11 +-
 src/test/ssl/t/SSL/Backend/OpenSSL.pm     |    4 +-
 src/test/ssl/t/SSL/Server.pm              |    5 +
 27 files changed, 4105 insertions(+), 201 deletions(-)
 create mode 100644 src/backend/libpq/be-secure-libressl.c
 create mode 100644 src/interfaces/libpq/fe-secure-libressl.c

diff --git a/configure b/configure
index 35b0b72f0a7..2e44fa676a8 100755
--- a/configure
+++ b/configure
@@ -12997,7 +12997,7 @@ fi
 #
 # SSL Library
 #
-# There is currently only one supported SSL/TLS library: OpenSSL.
+# The currently supported SSL/TLS libraries are: OpenSSL, LibreSSL
 #
 
 
@@ -13164,20 +13164,106 @@ else
 fi
 done
 
-  # Function introduced in OpenSSL 1.0.2, not in LibreSSL.
-  for ac_func in SSL_CTX_set_cert_cb
-do :
-  ac_fn_c_check_func "$LINENO" "SSL_CTX_set_cert_cb" "ac_cv_func_SSL_CTX_set_cert_cb"
-if test "x$ac_cv_func_SSL_CTX_set_cert_cb" = xyes; then :
+
+$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+
+elif test "$with_ssl" = libressl ; then
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking for CRYPTO_new_ex_data in -lcrypto" >&5
+$as_echo_n "checking for CRYPTO_new_ex_data in -lcrypto... " >&6; }
+if ${ac_cv_lib_crypto_CRYPTO_new_ex_data+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lcrypto  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char CRYPTO_new_ex_data ();
+int
+main ()
+{
+return CRYPTO_new_ex_data ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_crypto_CRYPTO_new_ex_data=yes
+else
+  ac_cv_lib_crypto_CRYPTO_new_ex_data=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_crypto_CRYPTO_new_ex_data" >&5
+$as_echo "$ac_cv_lib_crypto_CRYPTO_new_ex_data" >&6; }
+if test "x$ac_cv_lib_crypto_CRYPTO_new_ex_data" = xyes; then :
   cat >>confdefs.h <<_ACEOF
-#define HAVE_SSL_CTX_SET_CERT_CB 1
+#define HAVE_LIBCRYPTO 1
 _ACEOF
 
+  LIBS="-lcrypto $LIBS"
+
+else
+  as_fn_error $? "library 'crypto' is required for LibreSSL" "$LINENO" 5
 fi
-done
 
-  # Function introduced in OpenSSL 1.1.1, not in LibreSSL.
-  for ac_func in X509_get_signature_info SSL_CTX_set_num_tickets SSL_CTX_set_keylog_callback SSL_CTX_set_client_hello_cb
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_new in -lssl" >&5
+$as_echo_n "checking for SSL_new in -lssl... " >&6; }
+if ${ac_cv_lib_ssl_SSL_new+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_new ();
+int
+main ()
+{
+return SSL_new ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_ssl_SSL_new=yes
+else
+  ac_cv_lib_ssl_SSL_new=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl_SSL_new" >&5
+$as_echo "$ac_cv_lib_ssl_SSL_new" >&6; }
+if test "x$ac_cv_lib_ssl_SSL_new" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL 1
+_ACEOF
+
+  LIBS="-lssl $LIBS"
+
+else
+  as_fn_error $? "library 'ssl' is required for LibreSSL" "$LINENO" 5
+fi
+
+  # Functions introduced in LibreSSL 3.4.1 (OpenBSD 7.0)
+  for ac_func in SSL_CTX_set_ciphersuites SSL_CTX_use_certificate_chain_mem
 do :
   as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
 ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var"
@@ -13186,14 +13272,16 @@ if eval test \"x\$"$as_ac_var"\" = x"yes"; then :
 #define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1
 _ACEOF
 
+else
+  as_fn_error $? "LibreSSL version >= 3.4.1 is required for SSL support" "$LINENO" 5
 fi
 done
 
 
-$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+$as_echo "#define USE_LIBRESSL 1" >>confdefs.h
 
 elif test "$with_ssl" != no ; then
-  as_fn_error $? "--with-ssl must specify openssl" "$LINENO" 5
+  as_fn_error $? "--with-ssl must specify openssl or libressl" "$LINENO" 5
 fi
 
 
diff --git a/configure.ac b/configure.ac
index 0e624fe36b9..a5357d1d2a5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1420,7 +1420,7 @@ fi
 #
 # SSL Library
 #
-# There is currently only one supported SSL/TLS library: OpenSSL.
+# The currently supported SSL/TLS libraries are: OpenSSL, LibreSSL
 #
 PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl)])
 if test x"$with_ssl" = x"" ; then
@@ -1440,13 +1440,16 @@ if test "$with_ssl" = openssl ; then
   AC_CHECK_LIB(ssl,    SSL_new, [], [AC_MSG_ERROR([library 'ssl' is required for OpenSSL])])
   # Functions introduced in OpenSSL 1.1.1.
   AC_CHECK_FUNCS([SSL_CTX_set_ciphersuites], [], [AC_MSG_ERROR([OpenSSL version >= 1.1.1 is required for SSL support])])
-  # Function introduced in OpenSSL 1.0.2, not in LibreSSL.
-  AC_CHECK_FUNCS([SSL_CTX_set_cert_cb])
-  # Function introduced in OpenSSL 1.1.1, not in LibreSSL.
-  AC_CHECK_FUNCS([X509_get_signature_info SSL_CTX_set_num_tickets SSL_CTX_set_keylog_callback SSL_CTX_set_client_hello_cb])
   AC_DEFINE([USE_OPENSSL], 1, [Define to 1 to build with OpenSSL support. (--with-ssl=openssl)])
+elif test "$with_ssl" = libressl ; then
+  dnl Order matters!
+  AC_CHECK_LIB(crypto, CRYPTO_new_ex_data, [], [AC_MSG_ERROR([library 'crypto' is required for LibreSSL])])
+  AC_CHECK_LIB(ssl,    SSL_new, [], [AC_MSG_ERROR([library 'ssl' is required for LibreSSL])])
+  # Functions introduced in LibreSSL 3.4.1 (OpenBSD 7.0)
+  AC_CHECK_FUNCS([SSL_CTX_set_ciphersuites SSL_CTX_use_certificate_chain_mem], [], [AC_MSG_ERROR([LibreSSL version >= 3.4.1 is required for SSL support])])
+  AC_DEFINE([USE_LIBRESSL], 1, [Define to 1 to build with LibreSSL support. (--with-ssl=libressl)])
 elif test "$with_ssl" != no ; then
-  AC_MSG_ERROR([--with-ssl must specify openssl])
+  AC_MSG_ERROR([--with-ssl must specify openssl or libressl])
 fi
 AC_SUBST(with_ssl)
 
diff --git a/meson.build b/meson.build
index d88a7a70308..c84b68f999c 100644
--- a/meson.build
+++ b/meson.build
@@ -1627,8 +1627,8 @@ if sslopt == 'auto' and auto_features.disabled()
   sslopt = 'none'
 endif
 
-if sslopt in ['auto', 'openssl']
-  openssl_required = (sslopt == 'openssl')
+if sslopt in ['auto', 'openssl', 'libressl']
+  ssl_library_required = (sslopt != 'auto')
 
   # Try to find openssl via pkg-config et al, if that doesn't work
   # (e.g. because it's provided as part of the OS, like on FreeBSD), look for
@@ -1646,16 +1646,16 @@ if sslopt in ['auto', 'openssl']
       dirs: test_lib_d,
       header_include_directories: postgres_inc,
       has_headers: ['openssl/ssl.h', 'openssl/err.h'],
-      required: openssl_required)
+      required: ssl_library_required)
     crypto_lib = cc.find_library('crypto',
       dirs: test_lib_d,
-      required: openssl_required)
+      required: ssl_library_required)
     if ssl_lib.found() and crypto_lib.found()
       ssl_int = [ssl_lib, crypto_lib]
       ssl = declare_dependency(dependencies: ssl_int, include_directories: postgres_inc)
     endif
-  elif cc.has_header('openssl/ssl.h', args: test_c_args, dependencies: ssl, required: openssl_required) and \
-       cc.has_header('openssl/err.h', args: test_c_args, dependencies: ssl, required: openssl_required)
+  elif cc.has_header('openssl/ssl.h', args: test_c_args, dependencies: ssl, required: ssl_library_required) and \
+       cc.has_header('openssl/err.h', args: test_c_args, dependencies: ssl, required: ssl_library_required)
     ssl_int = [ssl]
   else
     ssl = not_found_dep
@@ -1666,41 +1666,52 @@ if sslopt in ['auto', 'openssl']
       ['CRYPTO_new_ex_data', {'required': true}],
       ['SSL_new', {'required': true}],
 
-      # Functions introduced in OpenSSL 1.1.1.
+      # Functions introduced in OpenSSL 1.1.1 and present in LibreSSL
       ['SSL_CTX_set_ciphersuites', {'required': true}],
 
+      # Functions for optionally supported functionality in OpenSSL
+      ['SSL_CTX_set_keylog_callback', {'optional': true}],
+
       # Function introduced in OpenSSL 1.0.2, not in LibreSSL.
-      ['SSL_CTX_set_cert_cb'],
+      ['SSL_CTX_set_cert_cb', {'openssl': true}],
 
-      # Function introduced in OpenSSL 1.1.1, not in LibreSSL.
-      ['X509_get_signature_info'],
-      ['SSL_CTX_set_num_tickets'],
-      ['SSL_CTX_set_keylog_callback'],
-      ['SSL_CTX_set_client_hello_cb'],
+      # Function present in LibreSSL 3.4, not in OpenSSL
+      ['SSL_CTX_use_certificate_chain_mem', {'libressl': true}],
     ]
 
-    are_openssl_funcs_complete = true
+    are_ssl_funcs_complete = true
     foreach c : check_funcs
       func = c.get(0)
       val = cc.has_function(func, args: test_c_args, dependencies: ssl_int)
       required = c.get(1, {}).get('required', false)
+      optional = c.get(1, {}).get('optional', false)
+      openssl_lib = c.get(1, {}).get('openssl', false)
+      libressl_lib = c.get(1, {}).get('libressl', false)
       if required and not val
-        are_openssl_funcs_complete = false
-        if openssl_required
-          error('openssl function @0@ is required'.format(func))
+        are_ssl_funcs_complete = false
+        if ssl_library_required
+          error('ssl function @0@ is required'.format(func))
         endif
         break
-      elif not required
+      elif optional
         cdata.set('HAVE_' + func.to_upper(), val ? 1 : false)
+      elif openssl_lib and val
+        ssl_library = 'openssl'
+      elif libressl_lib and val
+        ssl_library = 'libressl'
       endif
     endforeach
 
-    if are_openssl_funcs_complete
-      cdata.set('USE_OPENSSL', 1,
-                description: 'Define to 1 to build with OpenSSL support. (-Dssl=openssl)')
-      cdata.set('OPENSSL_API_COMPAT', '0x10101000L',
-                description: 'Define to the OpenSSL API version in use. This avoids deprecation warnings from newer OpenSSL versions.')
-      ssl_library = 'openssl'
+    if are_ssl_funcs_complete
+      if sslopt == 'openssl' and ssl_library == 'openssl'
+        cdata.set('USE_OPENSSL', 1,
+                  description: 'Define to 1 to build with OpenSSL support. (-Dssl=openssl)')
+        cdata.set('OPENSSL_API_COMPAT', '0x10101000L',
+                  description: 'Define to the OpenSSL API version in use. This avoids deprecation warnings from newer OpenSSL versions.')
+      elif sslopt == 'libressl' and ssl_library == 'libressl'
+        cdata.set('USE_LIBRESSL', 1,
+                  description: 'Define to 1 to build with LibreSSL support. (-Dssl=libressl)')
+      endif
     else
       ssl = not_found_dep
     endif
@@ -4331,13 +4342,13 @@ summary(
     'llvm': llvm,
     'lz4': lz4,
     'nls': libintl,
-    'openssl': ssl,
     'pam': pam,
     'plperl': [perl_dep, perlversion],
     'plpython': python3_dep,
     'pltcl': tcl_dep,
     'readline': readline,
     'selinux': selinux,
+    'ssl': [ssl_library, ssl],
     'systemd': systemd,
     'uuid': uuid,
     'zlib': zlib,
diff --git a/meson_options.txt b/meson_options.txt
index 6a793f3e479..a0601960fd9 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -148,9 +148,9 @@ option('readline', type: 'feature', value: 'auto',
 option('selinux', type: 'feature', value: 'auto',
   description: 'SELinux support')
 
-option('ssl', type: 'combo', choices: ['auto', 'none', 'openssl'],
+option('ssl', type: 'combo', choices: ['auto', 'none', 'openssl', 'libressl'],
   value: 'auto',
-  description: 'Use LIB for SSL/TLS support (openssl)')
+  description: 'Use LIB for SSL/TLS support')
 
 option('systemd', type: 'feature', value: 'auto',
   description: 'systemd support')
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 8afd252fc8c..19102147945 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1268,7 +1268,7 @@ check_ssl_sni(bool *newval, void **extra, GucSource source)
 		return false;
 	}
 #else
-#ifndef HAVE_SSL_CTX_SET_CLIENT_HELLO_CB
+#ifdef USE_LIBRESSL
 	if (*newval)
 	{
 		GUC_check_errmsg("SNI requires OpenSSL 1.1.1 or later");
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 98eb2a8242d..655737454f8 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -33,6 +33,9 @@ OBJS = \
 ifeq ($(with_ssl),openssl)
 OBJS += be-secure-openssl.o
 endif
+ifeq ($(with_ssl),libressl)
+OBJS += be-secure-libressl.o
+endif
 
 ifeq ($(with_gssapi),yes)
 OBJS += be-gssapi-common.o be-secure-gssapi.o
diff --git a/src/backend/libpq/be-secure-libressl.c b/src/backend/libpq/be-secure-libressl.c
new file mode 100644
index 00000000000..a720cb78fa4
--- /dev/null
+++ b/src/backend/libpq/be-secure-libressl.c
@@ -0,0 +1,1978 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-libressl.c
+ *	  functions for LibreSSL 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-libressl.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);
+
+static char *X509_NAME_to_cstring(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)
+{
+	MemoryContext oldcxt;
+	MemoryContext host_memcxt = NULL;
+	MemoryContextCallback *host_memcxt_cb;
+	int			res = HOSTSFILE_DISABLED;
+	struct hosts *new_hosts;
+	SSL_CTX    *context = NULL;
+	int			ssl_ver_min = -1;
+	int			ssl_ver_max = -1;
+
+	/*
+	 * 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);
+
+	/* ssl_sni is currently not supported by LibreSSL */
+	if (ssl_sni)
+	{
+		/*
+		 * The GUC check hook should have already blocked this but to be on
+		 * the safe side we doublecheck here.
+		 */
+		ereport(isServerStart ? FATAL : LOG,
+				errcode(ERRCODE_CONFIG_FILE_ERROR),
+				errmsg("ssl_sni is not supported with LibreSSL"));
+		goto error;
+	}
+
+	/* Since SNI is disabled, we load configuration from postgresql.conf. */
+	if (res == HOSTSFILE_DISABLED)
+	{
+		HostsLine  *pgconf = palloc0(sizeof(HostsLine));
+
+		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->default_host)
+	{
+		ereport(isServerStart ? FATAL : LOG,
+				errcode(ERRCODE_CONFIG_FILE_ERROR),
+				errmsg("no SSL configurations loaded"));
+		goto error;
+	}
+
+	/*
+	 * 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);
+
+	/*
+	 * 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.  LibreSSL only supports disabling
+	 * stateless tickets.
+	 */
+	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 LibreSSL since v4.
+	 * SSL_OP_NO_CLIENT_RENEGOTIATION is available in 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 LibreSSL 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 LibreSSL.
+ */
+static void
+host_context_cleanup_cb(void *arg)
+{
+	struct hosts *hosts = arg;
+
+	Assert(hosts->sni == NIL);
+	Assert(!hosts->no_sni);
+
+	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());
+
+	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 */
+	(*openssl_tls_init_hook) (ctx, isServerStart);
+
+	/*
+	 * 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;
+	}
+
+	/*
+	 * Install the client cert verification callback here if the user
+	 * configured a CA.
+	 */
+	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;
+	}
+
+	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_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;
+		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(x509name, NID_commonName, NULL, 0);
+		if (len != -1)
+		{
+			char	   *peer_cn;
+
+			peer_cn = MemoryContextAlloc(TopMemoryContext, len + 1);
+			r = X509_NAME_get_text_by_NID(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 LibreSSL 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;
+	}
+}
+
+/*
+ * 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;
+
+	/* 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.
+	 * introduced in OpenSSL 1.1.1, which can handle RSA-PSS signatures.
+	 */
+	if (!OBJ_find_sigid_algs(X509_get_signature_nid(server_cert),
+							 &algo_nid, NULL))
+		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(X509_NAME *name)
+{
+	BIO		   *membuf = BIO_new(BIO_s_mem());
+	int			i,
+				nid,
+				count = X509_NAME_entry_count(name);
+	X509_NAME_ENTRY *e;
+	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);
+	}
+}
+
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 4ce2a92b964..ce185428ad4 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -100,9 +100,7 @@ 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);
 
@@ -204,17 +202,6 @@ be_tls_init(bool isServerStart)
 	 */
 	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);
 
@@ -369,8 +356,6 @@ be_tls_init(bool isServerStart)
 		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
@@ -389,22 +374,6 @@ be_tls_init(bool isServerStart)
 						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
@@ -481,15 +450,10 @@ be_tls_init(bool isServerStart)
 	/*
 	 * 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.
+	 * only turns off stateless tickets, In order to turn off stateful tickets
+	 * we need SSL_CTX_set_num_tickets.
 	 */
-#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 */
@@ -501,17 +465,8 @@ be_tls_init(bool isServerStart)
 	/*
 	 * 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))
@@ -873,36 +828,14 @@ be_tls_open_server(Port *port)
 	}
 
 	/*
-	 * 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.
+	 * We use tje clienthello callback 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.
 	 */
-#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);
@@ -990,12 +923,8 @@ aloop:
 					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:
@@ -1810,7 +1739,6 @@ alpn_cb(SSL *ssl,
 	}
 }
 
-#ifdef HAVE_SSL_CTX_SET_CLIENT_HELLO_CB
 /*
  * ssl_update_ssl
  *
@@ -2057,7 +1985,6 @@ found:
 
 	return SSL_CLIENT_HELLO_SUCCESS;
 }
-#endif							/* HAVE_SSL_CTX_SET_CLIENT_HELLO_CB */
 
 /*
  * Set DH parameters for generating ephemeral DH keys.  The
@@ -2282,15 +2209,9 @@ be_tls_get_certificate_hash(Port *port, size_t *len)
 
 	/*
 	 * 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.
+	 * algorithm to use for the result.
 	 */
-#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");
 
 	/*
diff --git a/src/backend/libpq/meson.build b/src/backend/libpq/meson.build
index 8571f652844..d835d356335 100644
--- a/src/backend/libpq/meson.build
+++ b/src/backend/libpq/meson.build
@@ -18,7 +18,11 @@ backend_sources += files(
 )
 
 if ssl.found()
-  backend_sources += files('be-secure-openssl.c')
+  if ssl_library == 'openssl'
+    backend_sources += files('be-secure-openssl.c')
+  else
+    backend_sources += files('be-secure-libressl.c')
+  endif
 endif
 
 if gssapi.found()
diff --git a/src/include/common/openssl.h b/src/include/common/openssl.h
index 07e8bfbe4f0..c253ebe5e72 100644
--- a/src/include/common/openssl.h
+++ b/src/include/common/openssl.h
@@ -14,7 +14,7 @@
 #ifndef COMMON_OPENSSL_H
 #define COMMON_OPENSSL_H
 
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_LIBRESSL)
 #include <openssl/ssl.h>
 
 /*
@@ -38,6 +38,6 @@
 #define MAX_OPENSSL_TLS_VERSION  "TLSv1"
 #endif
 
-#endif							/* USE_OPENSSL */
+#endif							/* USE_OPENSSL, USE_LIBRESSL */
 
 #endif							/* COMMON_OPENSSL_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 921b2daa4ff..71925270690 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -21,7 +21,7 @@
 #include "common/scram-common.h"
 
 #include <sys/time.h>
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_LIBRESSL)
 #include <openssl/ssl.h>
 #include <openssl/err.h>
 #endif
@@ -218,7 +218,7 @@ typedef struct Port
 	 * (Although extensions should have no business accessing the raw_buf
 	 * fields anyway.)
 	 */
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_LIBRESSL)
 	SSL		   *ssl;
 	X509	   *peer;
 #else
@@ -331,12 +331,12 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
 extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
 
 /* init hook for SSL, the default sets the password callback if appropriate */
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_LIBRESSL)
 typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
 extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
 #endif
 
-#endif							/* USE_SSL */
+#endif							/* USE_SSL, USE_LIBRESSL */
 
 #ifdef ENABLE_GSS
 /*
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index d15073a0a93..05d80831715 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -123,12 +123,16 @@ extern PGDLLIMPORT bool ssl_loaded_verify_locations;
 #endif
 
 #ifdef USE_SSL
+#if defined(USE_OPENSSL)
 #define SSL_LIBRARY "OpenSSL"
+#elif defined(USE_LIBRESSL)
+#define SSL_LIBRARY "LibreSSL"
 #else
 #define SSL_LIBRARY ""
 #endif
+#endif
 
-#ifdef USE_OPENSSL
+#ifdef USE_SSL
 #define DEFAULT_SSL_CIPHERS "HIGH:MEDIUM:+3DES:!aNULL"
 #else
 #define DEFAULT_SSL_CIPHERS "none"
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 4f8113c144b..4bfab619a61 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -369,20 +369,12 @@
 /* Define to 1 if the system has the type `socklen_t'. */
 #undef HAVE_SOCKLEN_T
 
-/* Define to 1 if you have the `SSL_CTX_set_cert_cb' function. */
-#undef HAVE_SSL_CTX_SET_CERT_CB
-
 /* Define to 1 if you have the `SSL_CTX_set_ciphersuites' function. */
 #undef HAVE_SSL_CTX_SET_CIPHERSUITES
 
-/* Define to 1 if you have the `SSL_CTX_set_client_hello_cb' function. */
-#undef HAVE_SSL_CTX_SET_CLIENT_HELLO_CB
-
-/* Define to 1 if you have the `SSL_CTX_set_keylog_callback' function. */
-#undef HAVE_SSL_CTX_SET_KEYLOG_CALLBACK
-
-/* Define to 1 if you have the `SSL_CTX_set_num_tickets' function. */
-#undef HAVE_SSL_CTX_SET_NUM_TICKETS
+/* Define to 1 if you have the `SSL_CTX_use_certificate_chain_mem' function.
+   */
+#undef HAVE_SSL_CTX_USE_CERTIFICATE_CHAIN_MEM
 
 /* Define to 1 if you have the <stdint.h> header file. */
 #undef HAVE_STDINT_H
@@ -508,9 +500,6 @@
 /* Define to 1 if you have the `wcstombs_l' function. */
 #undef HAVE_WCSTOMBS_L
 
-/* Define to 1 if you have the `X509_get_signature_info' function. */
-#undef HAVE_X509_GET_SIGNATURE_INFO
-
 /* Define to 1 if the assembler supports X86_64's POPCNTQ instruction. */
 #undef HAVE_X86_64_POPCNTQ
 
@@ -707,6 +696,9 @@
 /* Define to build with NUMA support. (--with-libnuma) */
 #undef USE_LIBNUMA
 
+/* Define to 1 to build with LibreSSL support. (--with-ssl=libressl) */
+#undef USE_LIBRESSL
+
 /* Define to build with io_uring support. (--with-liburing) */
 #undef USE_LIBURING
 
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 521b49b8888..e01fee2a9af 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -173,7 +173,7 @@
  * USE_SSL code should be compiled only when compiling with an SSL
  * implementation.
  */
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_LIBRESSL)
 #define USE_SSL
 #endif
 
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 0963995eed4..ac925319bc6 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -56,6 +56,10 @@ ifeq ($(with_ssl),openssl)
 OBJS += \
 	fe-secure-openssl.o
 endif
+ifeq ($(with_ssl),libressl)
+OBJS += \
+	fe-secure-libressl.o
+endif
 
 ifeq ($(with_gssapi),yes)
 OBJS += \
diff --git a/src/interfaces/libpq/fe-secure-libressl.c b/src/interfaces/libpq/fe-secure-libressl.c
new file mode 100644
index 00000000000..22455c59af7
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-libressl.c
@@ -0,0 +1,1905 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-libressl.c
+ *	  LibreSSL support
+ *
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/interfaces/libpq/fe-secure-libressl.c
+ *
+ * NOTES
+ *
+ *	  We don't provide informational callbacks here (like
+ *	  info_cb() in be-secure-openssl.c), since there's no good mechanism to
+ *	  display such information to the user.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <signal.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <limits.h>
+
+#include "libpq-fe.h"
+#include "fe-auth.h"
+#include "fe-secure-common.h"
+#include "libpq-int.h"
+
+#ifdef WIN32
+#include "win32.h"
+#else
+#include <sys/socket.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#endif
+
+#include <sys/stat.h>
+
+#ifdef WIN32
+#include "pthread-win32.h"
+#else
+#include <pthread.h>
+#endif
+
+/*
+ * 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/ssl.h>
+#include <openssl/conf.h>
+#ifdef USE_SSL_ENGINE
+#include <openssl/engine.h>
+#endif
+#include <openssl/x509v3.h>
+
+
+static int	verify_cb(int ok, X509_STORE_CTX *ctx);
+static int	openssl_verify_peer_name_matches_certificate_name(PGconn *conn,
+															  ASN1_STRING *name_entry,
+															  char **store_name);
+static int	openssl_verify_peer_name_matches_certificate_ip(PGconn *conn,
+															ASN1_OCTET_STRING *addr_entry,
+															char **store_name);
+static int	initialize_SSL(PGconn *conn);
+static PostgresPollingStatusType open_client_SSL(PGconn *conn);
+static char *SSLerrmessage(unsigned long ecode);
+static void SSLerrfree(char *buf);
+static int	PQssl_passwd_cb(char *buf, int size, int rwflag, void *userdata);
+
+static int	pgconn_bio_read(BIO *h, char *buf, int size);
+static int	pgconn_bio_write(BIO *h, const char *buf, int size);
+static BIO_METHOD *pgconn_bio_method(void);
+static int	ssl_set_pgconn_bio(PGconn *conn);
+
+static pthread_mutex_t ssl_config_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+static PQsslKeyPassHook_OpenSSL_type PQsslKeyPassHook = NULL;
+static int	ssl_protocol_version_to_openssl(const char *protocol);
+
+/* ------------------------------------------------------------ */
+/*			 Procedures common to all secure sessions			*/
+/* ------------------------------------------------------------ */
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+	/* First time through? */
+	if (conn->ssl == NULL)
+	{
+		/*
+		 * Create a connection-specific SSL object, and load client
+		 * certificate, private key, and trusted CA certs.
+		 */
+		if (initialize_SSL(conn) != 0)
+		{
+			/* initialize_SSL already put a message in conn->errorMessage */
+			pgtls_close(conn);
+			return PGRES_POLLING_FAILED;
+		}
+	}
+
+	/* Begin or continue the actual handshake */
+	return open_client_SSL(conn);
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+	ssize_t		n;
+	int			result_errno = 0;
+	char		sebuf[PG_STRERROR_R_BUFLEN];
+	int			err;
+	unsigned long ecode;
+
+rloop:
+
+	/*
+	 * 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.  Since the possibility exists that other OpenSSL
+	 * clients running in the same thread but not under our control will fail
+	 * to call ERR_get_error() themselves (after their own I/O operations),
+	 * pro-actively clear the per-thread error queue now.
+	 */
+	SOCK_ERRNO_SET(0);
+	ERR_clear_error();
+	n = SSL_read(conn->ssl, ptr, len);
+	err = SSL_get_error(conn->ssl, n);
+
+	/*
+	 * Other clients of OpenSSL 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 != SSL_ERROR_NONE || n < 0) ? ERR_get_error() : 0;
+	switch (err)
+	{
+		case SSL_ERROR_NONE:
+			if (n < 0)
+			{
+				/* Not supposed to happen, so we don't translate the msg */
+				appendPQExpBufferStr(&conn->errorMessage,
+									 "SSL_read failed but did not provide error information\n");
+				/* assume the connection is broken */
+				result_errno = ECONNRESET;
+			}
+			break;
+		case SSL_ERROR_WANT_READ:
+			n = 0;
+			break;
+		case SSL_ERROR_WANT_WRITE:
+
+			/*
+			 * Returning 0 here would cause caller to wait for read-ready,
+			 * which is not correct since what SSL wants is wait for
+			 * write-ready.  The former could get us stuck in an infinite
+			 * wait, so don't risk it; busy-loop instead.
+			 */
+			goto rloop;
+		case SSL_ERROR_SYSCALL:
+			if (n < 0 && SOCK_ERRNO != 0)
+			{
+				result_errno = SOCK_ERRNO;
+				if (result_errno == EPIPE ||
+					result_errno == ECONNRESET)
+					libpq_append_conn_error(conn, "server closed the connection unexpectedly\n"
+											"\tThis probably means the server terminated abnormally\n"
+											"\tbefore or while processing the request.");
+				else
+					libpq_append_conn_error(conn, "SSL SYSCALL error: %s",
+											SOCK_STRERROR(result_errno,
+														  sebuf, sizeof(sebuf)));
+			}
+			else
+			{
+				libpq_append_conn_error(conn, "SSL SYSCALL error: EOF detected");
+				/* assume the connection is broken */
+				result_errno = ECONNRESET;
+				n = -1;
+			}
+			break;
+		case SSL_ERROR_SSL:
+			{
+				char	   *errm = SSLerrmessage(ecode);
+
+				libpq_append_conn_error(conn, "SSL error: %s", errm);
+				SSLerrfree(errm);
+				/* assume the connection is broken */
+				result_errno = ECONNRESET;
+				n = -1;
+				break;
+			}
+		case SSL_ERROR_ZERO_RETURN:
+
+			/*
+			 * Per OpenSSL documentation, this error code is only returned for
+			 * a clean connection closure, so we should not report it as a
+			 * server crash.
+			 */
+			libpq_append_conn_error(conn, "SSL connection has been closed unexpectedly");
+			result_errno = ECONNRESET;
+			n = -1;
+			break;
+		default:
+			libpq_append_conn_error(conn, "unrecognized SSL error code: %d", err);
+			/* assume the connection is broken */
+			result_errno = ECONNRESET;
+			n = -1;
+			break;
+	}
+
+	/* ensure we return the intended errno to caller */
+	SOCK_ERRNO_SET(result_errno);
+
+	return n;
+}
+
+ssize_t
+pgtls_bytes_pending(PGconn *conn)
+{
+	int			pending;
+
+	/*
+	 * OpenSSL readahead is documented to break SSL_pending().  Plus, we can't
+	 * afford to have OpenSSL take bytes off the socket without processing
+	 * them; that breaks the postconditions for pqsecure_drain_pending().
+	 */
+	Assert(!SSL_get_read_ahead(conn->ssl));
+
+	pending = SSL_pending(conn->ssl);
+	if (pending < 0)
+	{
+		/* shouldn't be possible */
+		Assert(false);
+		libpq_append_conn_error(conn, "LibreSSL reports negative bytes pending");
+		return -1;
+	}
+	else if (pending == INT_MAX)
+	{
+		/*
+		 * If we ever found a legitimate way to hit this, we'd need to loop
+		 * around in the caller to call pgtls_bytes_pending() again.  Throw an
+		 * error rather than complicate the code in that way, because
+		 * SSL_read() should be bounded to the size of a single TLS record,
+		 * and conn->inBuffer can't currently go past INT_MAX in size anyway.
+		 */
+		libpq_append_conn_error(conn, "LibreSSL reports INT_MAX bytes pending");
+		return -1;
+	}
+
+	return (ssize_t) pending;
+}
+
+
+ssize_t
+pgtls_write(PGconn *conn, const void *ptr, size_t len)
+{
+	ssize_t		n;
+	int			result_errno = 0;
+	char		sebuf[PG_STRERROR_R_BUFLEN];
+	int			err;
+	unsigned long ecode;
+
+	SOCK_ERRNO_SET(0);
+	ERR_clear_error();
+	n = SSL_write(conn->ssl, ptr, len);
+	err = SSL_get_error(conn->ssl, n);
+	ecode = (err != SSL_ERROR_NONE || n < 0) ? ERR_get_error() : 0;
+	switch (err)
+	{
+		case SSL_ERROR_NONE:
+			if (n < 0)
+			{
+				/* Not supposed to happen, so we don't translate the msg */
+				appendPQExpBufferStr(&conn->errorMessage,
+									 "SSL_write failed but did not provide error information\n");
+				/* assume the connection is broken */
+				result_errno = ECONNRESET;
+			}
+			break;
+		case SSL_ERROR_WANT_READ:
+
+			/*
+			 * Returning 0 here causes caller to wait for write-ready, which
+			 * is not really the right thing, but it's the best we can do.
+			 */
+			n = 0;
+			break;
+		case SSL_ERROR_WANT_WRITE:
+			n = 0;
+			break;
+		case SSL_ERROR_SYSCALL:
+
+			/*
+			 * If errno is still zero then assume it's a read EOF situation,
+			 * and report EOF.  (This seems possible because SSL_write can
+			 * also do reads.)
+			 */
+			if (n < 0 && SOCK_ERRNO != 0)
+			{
+				result_errno = SOCK_ERRNO;
+				if (result_errno == EPIPE || result_errno == ECONNRESET)
+					libpq_append_conn_error(conn, "server closed the connection unexpectedly\n"
+											"\tThis probably means the server terminated abnormally\n"
+											"\tbefore or while processing the request.");
+				else
+					libpq_append_conn_error(conn, "SSL SYSCALL error: %s",
+											SOCK_STRERROR(result_errno,
+														  sebuf, sizeof(sebuf)));
+			}
+			else
+			{
+				libpq_append_conn_error(conn, "SSL SYSCALL error: EOF detected");
+				/* assume the connection is broken */
+				result_errno = ECONNRESET;
+				n = -1;
+			}
+			break;
+		case SSL_ERROR_SSL:
+			{
+				char	   *errm = SSLerrmessage(ecode);
+
+				libpq_append_conn_error(conn, "SSL error: %s", errm);
+				SSLerrfree(errm);
+				/* assume the connection is broken */
+				result_errno = ECONNRESET;
+				n = -1;
+				break;
+			}
+		case SSL_ERROR_ZERO_RETURN:
+
+			/*
+			 * Per OpenSSL documentation, this error code is only returned for
+			 * a clean connection closure, so we should not report it as a
+			 * server crash.
+			 */
+			libpq_append_conn_error(conn, "SSL connection has been closed unexpectedly");
+			result_errno = ECONNRESET;
+			n = -1;
+			break;
+		default:
+			libpq_append_conn_error(conn, "unrecognized SSL error code: %d", err);
+			/* assume the connection is broken */
+			result_errno = ECONNRESET;
+			n = -1;
+			break;
+	}
+
+	/* ensure we return the intended errno to caller */
+	SOCK_ERRNO_SET(result_errno);
+
+	return n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+	X509	   *peer_cert;
+	const EVP_MD *algo_type;
+	unsigned char hash[EVP_MAX_MD_SIZE];	/* size for SHA-512 */
+	unsigned int hash_size;
+	int			algo_nid;
+	char	   *cert_hash;
+
+	*len = 0;
+
+	if (!conn->peer)
+		return NULL;
+
+	peer_cert = conn->peer;
+
+	/*
+	 * Get the signature algorithm of the certificate to determine the hash
+	 * algorithm to use for the result.
+	 */
+	if (!OBJ_find_sigid_algs(X509_get_signature_nid(peer_cert),
+							 &algo_nid, NULL))
+	{
+		libpq_append_conn_error(conn, "could not determine server certificate signature algorithm");
+		return NULL;
+	}
+
+	/*
+	 * 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)
+			{
+				libpq_append_conn_error(conn, "could not find digest for NID %s",
+										OBJ_nid2sn(algo_nid));
+				return NULL;
+			}
+			break;
+	}
+
+	if (!X509_digest(peer_cert, algo_type, hash, &hash_size))
+	{
+		libpq_append_conn_error(conn, "could not generate peer certificate hash");
+		return NULL;
+	}
+
+	/* save result */
+	cert_hash = malloc(hash_size);
+	if (cert_hash == NULL)
+	{
+		libpq_append_conn_error(conn, "out of memory");
+		return NULL;
+	}
+	memcpy(cert_hash, hash, hash_size);
+	*len = hash_size;
+
+	return cert_hash;
+}
+
+/* ------------------------------------------------------------ */
+/*						OpenSSL specific code					*/
+/* ------------------------------------------------------------ */
+
+/*
+ *	Certificate verification callback
+ *
+ *	This callback allows us to log intermediate problems during
+ *	verification, but there doesn't seem to be a clean way to get
+ *	our PGconn * structure.  So we can't log anything!
+ *
+ *	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)
+{
+	return ok;
+}
+
+/*
+ * OpenSSL-specific wrapper around
+ * pq_verify_peer_name_matches_certificate_name(), converting the ASN1_STRING
+ * into a plain C string.
+ */
+static int
+openssl_verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *name_entry,
+												  char **store_name)
+{
+	int			len;
+	const unsigned char *namedata;
+
+	/* Should not happen... */
+	if (name_entry == NULL)
+	{
+		libpq_append_conn_error(conn, "SSL certificate's name entry is missing");
+		return -1;
+	}
+
+	/*
+	 * GEN_DNS can be only IA5String, equivalent to US ASCII.
+	 */
+	namedata = ASN1_STRING_get0_data(name_entry);
+	len = ASN1_STRING_length(name_entry);
+
+	/* OK to cast from unsigned to plain char, since it's all ASCII. */
+	return pq_verify_peer_name_matches_certificate_name(conn, (const char *) namedata, len, store_name);
+}
+
+/*
+ * OpenSSL-specific wrapper around
+ * pq_verify_peer_name_matches_certificate_ip(), converting the
+ * ASN1_OCTET_STRING into a plain C string.
+ */
+static int
+openssl_verify_peer_name_matches_certificate_ip(PGconn *conn,
+												ASN1_OCTET_STRING *addr_entry,
+												char **store_name)
+{
+	int			len;
+	const unsigned char *addrdata;
+
+	/* Should not happen... */
+	if (addr_entry == NULL)
+	{
+		libpq_append_conn_error(conn, "SSL certificate's address entry is missing");
+		return -1;
+	}
+
+	/*
+	 * GEN_IPADD is an OCTET STRING containing an IP address in network byte
+	 * order.
+	 */
+	addrdata = ASN1_STRING_get0_data(addr_entry);
+	len = ASN1_STRING_length(addr_entry);
+
+	return pq_verify_peer_name_matches_certificate_ip(conn, addrdata, len, store_name);
+}
+
+static bool
+is_ip_address(const char *host)
+{
+	struct in_addr dummy4;
+#ifdef HAVE_INET_PTON
+	struct in6_addr dummy6;
+#endif
+
+	return inet_aton(host, &dummy4)
+#ifdef HAVE_INET_PTON
+		|| (inet_pton(AF_INET6, host, &dummy6) == 1)
+#endif
+		;
+}
+
+/*
+ *	Verify that the server certificate matches the hostname we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+												int *names_examined,
+												char **first_name)
+{
+	STACK_OF(GENERAL_NAME) * peer_san;
+	int			i;
+	int			rc = 0;
+	char	   *host = conn->connhost[conn->whichhost].host;
+	int			host_type;
+	bool		check_cn = true;
+
+	Assert(host && host[0]);	/* should be guaranteed by caller */
+
+	/*
+	 * We try to match the NSS behavior here, which is a slight departure from
+	 * the spec but seems to make more intuitive sense:
+	 *
+	 * If connhost contains a DNS name, and the certificate's SANs contain any
+	 * dNSName entries, then we'll ignore the Subject Common Name entirely;
+	 * otherwise, we fall back to checking the CN. (This behavior matches the
+	 * RFC.)
+	 *
+	 * If connhost contains an IP address, and the SANs contain iPAddress
+	 * entries, we again ignore the CN. Otherwise, we allow the CN to match,
+	 * EVEN IF there is a dNSName in the SANs. (RFC 6125 prohibits this: "A
+	 * client MUST NOT seek a match for a reference identifier of CN-ID if the
+	 * presented identifiers include a DNS-ID, SRV-ID, URI-ID, or any
+	 * application-specific identifier types supported by the client.")
+	 *
+	 * NOTE: Prior versions of libpq did not consider iPAddress entries at
+	 * all, so this new behavior might break a certificate that has different
+	 * IP addresses in the Subject CN and the SANs.
+	 */
+	if (is_ip_address(host))
+		host_type = GEN_IPADD;
+	else
+		host_type = GEN_DNS;
+
+	/*
+	 * First, get the Subject Alternative Names (SANs) from the certificate,
+	 * and compare them against the originally given hostname.
+	 */
+	peer_san = (STACK_OF(GENERAL_NAME) *)
+		X509_get_ext_d2i(conn->peer, NID_subject_alt_name, NULL, NULL);
+
+	if (peer_san)
+	{
+		int			san_len = sk_GENERAL_NAME_num(peer_san);
+
+		for (i = 0; i < san_len; i++)
+		{
+			const GENERAL_NAME *name = sk_GENERAL_NAME_value(peer_san, i);
+			char	   *alt_name = NULL;
+
+			if (name->type == host_type)
+			{
+				/*
+				 * This SAN is of the same type (IP or DNS) as our host name,
+				 * so don't allow a fallback check of the CN.
+				 */
+				check_cn = false;
+			}
+
+			if (name->type == GEN_DNS)
+			{
+				(*names_examined)++;
+				rc = openssl_verify_peer_name_matches_certificate_name(conn,
+																	   name->d.dNSName,
+																	   &alt_name);
+			}
+			else if (name->type == GEN_IPADD)
+			{
+				(*names_examined)++;
+				rc = openssl_verify_peer_name_matches_certificate_ip(conn,
+																	 name->d.iPAddress,
+																	 &alt_name);
+			}
+
+			if (alt_name)
+			{
+				if (!*first_name)
+					*first_name = alt_name;
+				else
+					free(alt_name);
+			}
+
+			if (rc != 0)
+			{
+				/*
+				 * Either we hit an error or a match, and either way we should
+				 * not fall back to the CN.
+				 */
+				check_cn = false;
+				break;
+			}
+		}
+		sk_GENERAL_NAME_pop_free(peer_san, GENERAL_NAME_free);
+	}
+
+	/*
+	 * If there is no subjectAltName extension of the matching type, check the
+	 * Common Name.
+	 *
+	 * (Per RFC 2818 and RFC 6125, if the subjectAltName extension of type
+	 * dNSName is present, the CN must be ignored. We break this rule if host
+	 * is an IP address; see the comment above.)
+	 */
+	if (check_cn)
+	{
+		X509_NAME  *subject_name;
+
+		subject_name = X509_get_subject_name(conn->peer);
+		if (subject_name != NULL)
+		{
+			int			cn_index;
+
+			cn_index = X509_NAME_get_index_by_NID(subject_name,
+												  NID_commonName, -1);
+			if (cn_index >= 0)
+			{
+				char	   *common_name = NULL;
+
+				(*names_examined)++;
+				rc = openssl_verify_peer_name_matches_certificate_name(conn,
+																	   X509_NAME_ENTRY_get_data(X509_NAME_get_entry(subject_name, cn_index)),
+																	   &common_name);
+
+				if (common_name)
+				{
+					if (!*first_name)
+						*first_name = common_name;
+					else
+						free(common_name);
+				}
+			}
+		}
+	}
+
+	return rc;
+}
+
+/* See pqcomm.h comments on OpenSSL implementation of ALPN (RFC 7301) */
+static unsigned char alpn_protos[] = PG_ALPN_PROTOCOL_VECTOR;
+
+/*
+ *	Create per-connection SSL object, and load the client certificate,
+ *	private key, and trusted CA certs.
+ *
+ *	Returns 0 if OK, -1 on failure (with a message in conn->errorMessage).
+ */
+static int
+initialize_SSL(PGconn *conn)
+{
+	SSL_CTX    *SSL_context;
+	struct stat buf;
+	char		homedir[MAXPGPATH];
+	char		fnbuf[MAXPGPATH];
+	char		sebuf[PG_STRERROR_R_BUFLEN];
+	bool		have_homedir;
+	bool		have_cert;
+	bool		have_rootcert;
+
+	/*
+	 * We'll need the home directory if any of the relevant parameters are
+	 * defaulted.  If pqGetHomeDirectory fails, act as though none of the
+	 * files could be found.
+	 */
+	if (!(conn->sslcert && strlen(conn->sslcert) > 0) ||
+		!(conn->sslkey && strlen(conn->sslkey) > 0) ||
+		!(conn->sslrootcert && strlen(conn->sslrootcert) > 0) ||
+		!((conn->sslcrl && strlen(conn->sslcrl) > 0) ||
+		  (conn->sslcrldir && strlen(conn->sslcrldir) > 0)))
+		have_homedir = pqGetHomeDirectory(homedir, sizeof(homedir));
+	else						/* won't need it */
+		have_homedir = false;
+
+	/*
+	 * Create a new SSL_CTX object.
+	 *
+	 * We used to share a single SSL_CTX between all connections, but it was
+	 * complicated if connections used different certificates. So now we
+	 * create a separate context for each connection, and accept the overhead.
+	 */
+	SSL_context = SSL_CTX_new(SSLv23_method());
+	if (!SSL_context)
+	{
+		char	   *err = SSLerrmessage(ERR_get_error());
+
+		libpq_append_conn_error(conn, "could not create SSL context: %s", err);
+		SSLerrfree(err);
+		return -1;
+	}
+
+	/*
+	 * Delegate the client cert password prompt to the libpq wrapper callback
+	 * if any is defined.
+	 *
+	 * If the application hasn't installed its own and the sslpassword
+	 * parameter is non-null, we install ours now to make sure we supply
+	 * PGconn->sslpassword to OpenSSL instead of letting it prompt on stdin.
+	 *
+	 * This will replace OpenSSL's default PEM_def_callback (which prompts on
+	 * stdin), but we're only setting it for this SSL context so it's
+	 * harmless.
+	 */
+	if (PQsslKeyPassHook
+		|| (conn->sslpassword && strlen(conn->sslpassword) > 0))
+	{
+		SSL_CTX_set_default_passwd_cb(SSL_context, PQssl_passwd_cb);
+		SSL_CTX_set_default_passwd_cb_userdata(SSL_context, conn);
+	}
+
+	/* Disable old protocol versions */
+	SSL_CTX_set_options(SSL_context, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
+
+	/* Set the minimum and maximum protocol versions if necessary */
+	if (conn->ssl_min_protocol_version &&
+		strlen(conn->ssl_min_protocol_version) != 0)
+	{
+		int			ssl_min_ver;
+
+		ssl_min_ver = ssl_protocol_version_to_openssl(conn->ssl_min_protocol_version);
+
+		if (ssl_min_ver == -1)
+		{
+			libpq_append_conn_error(conn, "invalid value \"%s\" for minimum SSL protocol version",
+									conn->ssl_min_protocol_version);
+			SSL_CTX_free(SSL_context);
+			return -1;
+		}
+
+		if (!SSL_CTX_set_min_proto_version(SSL_context, ssl_min_ver))
+		{
+			char	   *err = SSLerrmessage(ERR_get_error());
+
+			libpq_append_conn_error(conn, "could not set minimum SSL protocol version: %s", err);
+			SSLerrfree(err);
+			SSL_CTX_free(SSL_context);
+			return -1;
+		}
+	}
+
+	if (conn->ssl_max_protocol_version &&
+		strlen(conn->ssl_max_protocol_version) != 0)
+	{
+		int			ssl_max_ver;
+
+		ssl_max_ver = ssl_protocol_version_to_openssl(conn->ssl_max_protocol_version);
+
+		if (ssl_max_ver == -1)
+		{
+			libpq_append_conn_error(conn, "invalid value \"%s\" for maximum SSL protocol version",
+									conn->ssl_max_protocol_version);
+			SSL_CTX_free(SSL_context);
+			return -1;
+		}
+
+		if (!SSL_CTX_set_max_proto_version(SSL_context, ssl_max_ver))
+		{
+			char	   *err = SSLerrmessage(ERR_get_error());
+
+			libpq_append_conn_error(conn, "could not set maximum SSL protocol version: %s", err);
+			SSLerrfree(err);
+			SSL_CTX_free(SSL_context);
+			return -1;
+		}
+	}
+
+	/*
+	 * Disable OpenSSL's moving-write-buffer sanity check, because it causes
+	 * unnecessary failures in nonblocking send cases.
+	 */
+	SSL_CTX_set_mode(SSL_context, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
+
+	/*
+	 * If the root cert file exists, load it so we can perform certificate
+	 * verification. If sslmode is "verify-full" we will also do further
+	 * verification after the connection has been completed.
+	 */
+	if (conn->sslrootcert && strlen(conn->sslrootcert) > 0)
+		strlcpy(fnbuf, conn->sslrootcert, sizeof(fnbuf));
+	else if (have_homedir)
+		snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, ROOT_CERT_FILE);
+	else
+		fnbuf[0] = '\0';
+
+	if (strcmp(fnbuf, "system") == 0)
+	{
+		/*
+		 * The "system" sentinel value indicates that we should load whatever
+		 * root certificates are installed for use by OpenSSL; these locations
+		 * differ by platform. Note that the default system locations may be
+		 * further overridden by the SSL_CERT_DIR and SSL_CERT_FILE
+		 * environment variables.
+		 */
+		if (SSL_CTX_set_default_verify_paths(SSL_context) != 1)
+		{
+			char	   *err = SSLerrmessage(ERR_get_error());
+
+			libpq_append_conn_error(conn, "could not load system root certificate paths: %s",
+									err);
+			SSLerrfree(err);
+			SSL_CTX_free(SSL_context);
+			return -1;
+		}
+		have_rootcert = true;
+	}
+	else if (fnbuf[0] != '\0' &&
+			 stat(fnbuf, &buf) == 0)
+	{
+		X509_STORE *cvstore;
+
+		if (SSL_CTX_load_verify_locations(SSL_context, fnbuf, NULL) != 1)
+		{
+			char	   *err = SSLerrmessage(ERR_get_error());
+
+			libpq_append_conn_error(conn, "could not read root certificate file \"%s\": %s",
+									fnbuf, err);
+			SSLerrfree(err);
+			SSL_CTX_free(SSL_context);
+			return -1;
+		}
+
+		if ((cvstore = SSL_CTX_get_cert_store(SSL_context)) != NULL)
+		{
+			char	   *fname = NULL;
+			char	   *dname = NULL;
+
+			if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+				fname = conn->sslcrl;
+			if (conn->sslcrldir && strlen(conn->sslcrldir) > 0)
+				dname = conn->sslcrldir;
+
+			/* defaults to use the default CRL file */
+			if (!fname && !dname && have_homedir)
+			{
+				snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, ROOT_CRL_FILE);
+				fname = fnbuf;
+			}
+
+			/* Set the flags to check against the complete CRL chain */
+			if ((fname || dname) &&
+				X509_STORE_load_locations(cvstore, fname, dname) == 1)
+			{
+				X509_STORE_set_flags(cvstore,
+									 X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL);
+			}
+
+			/* if not found, silently ignore;  we do not require CRL */
+			ERR_clear_error();
+		}
+		have_rootcert = true;
+	}
+	else
+	{
+		/*
+		 * stat() failed; assume root file doesn't exist.  If sslmode is
+		 * verify-ca or verify-full, this is an error.  Otherwise, continue
+		 * without performing any server cert verification.
+		 */
+		if (conn->sslmode[0] == 'v')	/* "verify-ca" or "verify-full" */
+		{
+			/*
+			 * The only way to reach here with an empty filename is if
+			 * pqGetHomeDirectory failed.  That's a sufficiently unusual case
+			 * that it seems worth having a specialized error message for it.
+			 */
+			if (fnbuf[0] == '\0')
+				libpq_append_conn_error(conn, "could not get home directory to locate root certificate file\n"
+										"Either provide the file, use the system's trusted roots with sslrootcert=system, or change sslmode to disable server certificate verification.");
+			else
+				libpq_append_conn_error(conn, "root certificate file \"%s\" does not exist\n"
+										"Either provide the file, use the system's trusted roots with sslrootcert=system, or change sslmode to disable server certificate verification.", fnbuf);
+			SSL_CTX_free(SSL_context);
+			return -1;
+		}
+		have_rootcert = false;
+	}
+
+	/* Read the client certificate file */
+	if (conn->sslcert && strlen(conn->sslcert) > 0)
+		strlcpy(fnbuf, conn->sslcert, sizeof(fnbuf));
+	else if (have_homedir)
+		snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_CERT_FILE);
+	else
+		fnbuf[0] = '\0';
+
+	if (conn->sslcertmode[0] == 'd')	/* disable */
+	{
+		/* don't send a client cert even if we have one */
+		have_cert = false;
+	}
+	else if (fnbuf[0] == '\0')
+	{
+		/* no home directory, proceed without a client cert */
+		have_cert = false;
+	}
+	else if (stat(fnbuf, &buf) != 0)
+	{
+		/*
+		 * If file is not present, just go on without a client cert; server
+		 * might or might not accept the connection.  Any other error,
+		 * however, is grounds for complaint.
+		 */
+		if (errno != ENOENT && errno != ENOTDIR)
+		{
+			libpq_append_conn_error(conn, "could not open certificate file \"%s\": %s",
+									fnbuf, strerror_r(errno, sebuf, sizeof(sebuf)));
+			SSL_CTX_free(SSL_context);
+			return -1;
+		}
+		have_cert = false;
+	}
+	else
+	{
+		/*
+		 * Cert file exists, so load it. Since OpenSSL doesn't provide the
+		 * equivalent of "SSL_use_certificate_chain_file", we have to load it
+		 * into the SSL context, rather than the SSL object.
+		 */
+		if (SSL_CTX_use_certificate_chain_file(SSL_context, fnbuf) != 1)
+		{
+			char	   *err = SSLerrmessage(ERR_get_error());
+
+			libpq_append_conn_error(conn, "could not read certificate file \"%s\": %s",
+									fnbuf, err);
+			SSLerrfree(err);
+			SSL_CTX_free(SSL_context);
+			return -1;
+		}
+
+		/* need to load the associated private key, too */
+		have_cert = true;
+	}
+
+	/*
+	 * The SSL context is now loaded with the correct root and client
+	 * certificates. Create a connection-specific SSL object. The private key
+	 * is loaded directly into the SSL object. (We could load the private key
+	 * into the context, too, but we have done it this way historically, and
+	 * it doesn't really matter.)
+	 */
+	if (!(conn->ssl = SSL_new(SSL_context)) ||
+		!SSL_set_app_data(conn->ssl, conn) ||
+		!ssl_set_pgconn_bio(conn))
+	{
+		char	   *err = SSLerrmessage(ERR_get_error());
+
+		libpq_append_conn_error(conn, "could not establish SSL connection: %s", err);
+		SSLerrfree(err);
+		SSL_CTX_free(SSL_context);
+		return -1;
+	}
+	conn->ssl_in_use = true;
+
+	/*
+	 * If SSL key logging is requested, set up the callback if a compatible
+	 * version of OpenSSL is used and libpq was compiled to support it.
+	 */
+	if (conn->sslkeylogfile && strlen(conn->sslkeylogfile) > 0)
+		fprintf(stderr, libpq_gettext("WARNING: sslkeylogfile support requires OpenSSL\n"));
+
+	/*
+	 * SSL contexts are reference counted by OpenSSL. We can free it as soon
+	 * as we have created the SSL object, and it will stick around for as long
+	 * as it's actually needed.
+	 */
+	SSL_CTX_free(SSL_context);
+	SSL_context = NULL;
+
+	/*
+	 * Set Server Name Indication (SNI), if enabled by connection parameters.
+	 * Per RFC 6066, do not set it if the host is a literal IP address (IPv4
+	 * or IPv6).
+	 */
+	if (conn->sslsni && conn->sslsni[0] == '1')
+	{
+		const char *host = conn->connhost[conn->whichhost].host;
+
+		if (host && host[0] &&
+			!(strspn(host, "0123456789.") == strlen(host) ||
+			  strchr(host, ':')))
+		{
+			if (SSL_set_tlsext_host_name(conn->ssl, host) != 1)
+			{
+				char	   *err = SSLerrmessage(ERR_get_error());
+
+				libpq_append_conn_error(conn, "could not set SSL Server Name Indication (SNI): %s", err);
+				SSLerrfree(err);
+				return -1;
+			}
+		}
+	}
+
+	/* Set ALPN */
+	{
+		int			retval;
+
+		retval = SSL_set_alpn_protos(conn->ssl, alpn_protos, sizeof(alpn_protos));
+
+		if (retval != 0)
+		{
+			char	   *err = SSLerrmessage(ERR_get_error());
+
+			libpq_append_conn_error(conn, "could not set SSL ALPN extension: %s", err);
+			SSLerrfree(err);
+			return -1;
+		}
+	}
+
+	/*
+	 * Read the SSL key. If a key is specified, treat it as an engine:key
+	 * combination if there is colon present - we don't support files with
+	 * colon in the name. The exception is if the second character is a colon,
+	 * in which case it can be a Windows filename with drive specification.
+	 */
+	if (have_cert && conn->sslkey && strlen(conn->sslkey) > 0)
+	{
+#ifdef USE_SSL_ENGINE
+		if (strchr(conn->sslkey, ':')
+#ifdef WIN32
+			&& conn->sslkey[1] != ':'
+#endif
+			)
+		{
+			/* Colon, but not in second character, treat as engine:key */
+			char	   *engine_str = strdup(conn->sslkey);
+			char	   *engine_colon;
+			EVP_PKEY   *pkey;
+
+			if (engine_str == NULL)
+			{
+				libpq_append_conn_error(conn, "out of memory");
+				return -1;
+			}
+
+			/* cannot return NULL because we already checked before strdup */
+			engine_colon = strchr(engine_str, ':');
+
+			*engine_colon = '\0';	/* engine_str now has engine name */
+			engine_colon++;		/* engine_colon now has key name */
+
+			conn->engine = ENGINE_by_id(engine_str);
+			if (conn->engine == NULL)
+			{
+				char	   *err = SSLerrmessage(ERR_get_error());
+
+				libpq_append_conn_error(conn, "could not load SSL engine \"%s\": %s",
+										engine_str, err);
+				SSLerrfree(err);
+				free(engine_str);
+				return -1;
+			}
+
+			if (ENGINE_init(conn->engine) == 0)
+			{
+				char	   *err = SSLerrmessage(ERR_get_error());
+
+				libpq_append_conn_error(conn, "could not initialize SSL engine \"%s\": %s",
+										engine_str, err);
+				SSLerrfree(err);
+				ENGINE_free(conn->engine);
+				conn->engine = NULL;
+				free(engine_str);
+				return -1;
+			}
+
+			pkey = ENGINE_load_private_key(conn->engine, engine_colon,
+										   NULL, NULL);
+			if (pkey == NULL)
+			{
+				char	   *err = SSLerrmessage(ERR_get_error());
+
+				libpq_append_conn_error(conn, "could not read private SSL key \"%s\" from engine \"%s\": %s",
+										engine_colon, engine_str, err);
+				SSLerrfree(err);
+				ENGINE_finish(conn->engine);
+				ENGINE_free(conn->engine);
+				conn->engine = NULL;
+				free(engine_str);
+				return -1;
+			}
+			if (SSL_use_PrivateKey(conn->ssl, pkey) != 1)
+			{
+				char	   *err = SSLerrmessage(ERR_get_error());
+
+				libpq_append_conn_error(conn, "could not load private SSL key \"%s\" from engine \"%s\": %s",
+										engine_colon, engine_str, err);
+				SSLerrfree(err);
+				ENGINE_finish(conn->engine);
+				ENGINE_free(conn->engine);
+				conn->engine = NULL;
+				free(engine_str);
+				return -1;
+			}
+
+			free(engine_str);
+
+			fnbuf[0] = '\0';	/* indicate we're not going to load from a
+								 * file */
+		}
+		else
+#endif							/* USE_SSL_ENGINE */
+		{
+			/* PGSSLKEY is not an engine, treat it as a filename */
+			strlcpy(fnbuf, conn->sslkey, sizeof(fnbuf));
+		}
+	}
+	else if (have_homedir)
+	{
+		/* No PGSSLKEY specified, load default file */
+		snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_KEY_FILE);
+	}
+	else
+		fnbuf[0] = '\0';
+
+	if (have_cert && fnbuf[0] != '\0')
+	{
+		/* read the client key from file */
+
+		if (stat(fnbuf, &buf) != 0)
+		{
+			if (errno == ENOENT)
+				libpq_append_conn_error(conn, "certificate present, but not private key file \"%s\"",
+										fnbuf);
+			else
+				libpq_append_conn_error(conn, "could not stat private key file \"%s\": %m",
+										fnbuf);
+			return -1;
+		}
+
+		/* Key file must be a regular file */
+		if (!S_ISREG(buf.st_mode))
+		{
+			libpq_append_conn_error(conn, "private key file \"%s\" is not a regular file",
+									fnbuf);
+			return -1;
+		}
+
+		/*
+		 * Refuse to load world-readable key files.  We accept root-owned
+		 * files with mode 0640 or less, so that we can access system-wide
+		 * certificates if we have a supplementary group membership that
+		 * allows us to read 'em.  For files with non-root ownership, require
+		 * mode 0600 or less.  We need not check the file's ownership exactly;
+		 * if we're able to read it despite it having such restrictive
+		 * permissions, it must have the right ownership.
+		 *
+		 * Note: be very careful about tightening these rules.  Some people
+		 * expect, for example, that a client process running as root should
+		 * be able to use a non-root-owned key file.
+		 *
+		 * Note that roughly similar checks are performed in
+		 * src/backend/libpq/be-secure-common.c so any changes here may need
+		 * to be made there as well.  However, this code caters for the case
+		 * of current user == root, while that code does not.
+		 *
+		 * Ideally we would do similar permissions checks on Windows, but it
+		 * is not clear how that would work since Unix-style permissions may
+		 * not be available.
+		 */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+		if (buf.st_uid == 0 ?
+			buf.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO) :
+			buf.st_mode & (S_IRWXG | S_IRWXO))
+		{
+			libpq_append_conn_error(conn,
+									"private key file \"%s\" has group or world access; file must have permissions u=rw (0600) or less if owned by the current user, or permissions u=rw,g=r (0640) or less if owned by root",
+									fnbuf);
+			return -1;
+		}
+#endif
+
+		if (SSL_use_PrivateKey_file(conn->ssl, fnbuf, SSL_FILETYPE_PEM) != 1)
+		{
+			char	   *err = SSLerrmessage(ERR_get_error());
+
+			/*
+			 * We'll try to load the file in DER (binary ASN.1) format, and if
+			 * that fails too, report the original error. This could mask
+			 * issues where there's something wrong with a DER-format cert,
+			 * but we'd have to duplicate openssl's format detection to be
+			 * smarter than this. We can't just probe for a leading -----BEGIN
+			 * because PEM can have leading non-matching lines and blanks.
+			 * OpenSSL doesn't expose its get_name(...) and its PEM routines
+			 * don't differentiate between failure modes in enough detail to
+			 * let us tell the difference between "not PEM, try DER" and
+			 * "wrong password".
+			 */
+			if (SSL_use_PrivateKey_file(conn->ssl, fnbuf, SSL_FILETYPE_ASN1) != 1)
+			{
+				libpq_append_conn_error(conn, "could not load private key file \"%s\": %s",
+										fnbuf, err);
+				SSLerrfree(err);
+				return -1;
+			}
+
+			SSLerrfree(err);
+		}
+	}
+
+	/* verify that the cert and key go together */
+	if (have_cert &&
+		SSL_check_private_key(conn->ssl) != 1)
+	{
+		char	   *err = SSLerrmessage(ERR_get_error());
+
+		libpq_append_conn_error(conn, "certificate does not match private key file \"%s\": %s",
+								fnbuf, err);
+		SSLerrfree(err);
+		return -1;
+	}
+
+	/*
+	 * If a root cert was loaded, also set our certificate verification
+	 * callback.
+	 */
+	if (have_rootcert)
+		SSL_set_verify(conn->ssl, SSL_VERIFY_PEER, verify_cb);
+
+	/*
+	 * Set compression option if necessary.
+	 */
+	if (conn->sslcompression && conn->sslcompression[0] == '0')
+		SSL_set_options(conn->ssl, SSL_OP_NO_COMPRESSION);
+	else
+		SSL_clear_options(conn->ssl, SSL_OP_NO_COMPRESSION);
+
+	return 0;
+}
+
+/*
+ *	Attempt to negotiate SSL connection.
+ */
+static PostgresPollingStatusType
+open_client_SSL(PGconn *conn)
+{
+	int			r;
+
+	SOCK_ERRNO_SET(0);
+	ERR_clear_error();
+	r = SSL_connect(conn->ssl);
+	if (r <= 0)
+	{
+		int			save_errno = SOCK_ERRNO;
+		int			err = SSL_get_error(conn->ssl, r);
+		unsigned long ecode;
+
+		ecode = ERR_get_error();
+		switch (err)
+		{
+			case SSL_ERROR_WANT_READ:
+				return PGRES_POLLING_READING;
+
+			case SSL_ERROR_WANT_WRITE:
+				return PGRES_POLLING_WRITING;
+
+			case SSL_ERROR_SYSCALL:
+				{
+					char		sebuf[PG_STRERROR_R_BUFLEN];
+					unsigned long vcode;
+
+					vcode = SSL_get_verify_result(conn->ssl);
+
+					/*
+					 * If we get an X509 error here for failing to load the
+					 * local issuer cert, without an error in the socket layer
+					 * it means that verification failed due to a missing
+					 * system CA pool without it being a protocol error. We
+					 * inspect the sslrootcert setting to ensure that the user
+					 * was using the system CA pool. For other errors, log
+					 * them using the normal SYSCALL logging.
+					 */
+					if (save_errno == 0 &&
+						vcode == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY &&
+						strcmp(conn->sslrootcert, "system") == 0)
+						libpq_append_conn_error(conn, "SSL error: certificate verify failed: %s",
+												X509_verify_cert_error_string(vcode));
+					else if (r == -1 && save_errno != 0)
+						libpq_append_conn_error(conn, "SSL SYSCALL error: %s",
+												SOCK_STRERROR(save_errno, sebuf, sizeof(sebuf)));
+					else
+						libpq_append_conn_error(conn, "SSL SYSCALL error: EOF detected");
+					pgtls_close(conn);
+					return PGRES_POLLING_FAILED;
+				}
+			case SSL_ERROR_SSL:
+				{
+					char	   *err = SSLerrmessage(ecode);
+
+					libpq_append_conn_error(conn, "SSL error: %s", err);
+					SSLerrfree(err);
+					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:
+						case SSL_R_VERSION_TOO_LOW:
+#endif
+							libpq_append_conn_error(conn, "This may indicate that the server does not support any SSL protocol version between %s and %s.",
+													conn->ssl_min_protocol_version ?
+													conn->ssl_min_protocol_version :
+													MIN_OPENSSL_TLS_VERSION,
+													conn->ssl_max_protocol_version ?
+													conn->ssl_max_protocol_version :
+													MAX_OPENSSL_TLS_VERSION);
+							break;
+						default:
+							break;
+					}
+					pgtls_close(conn);
+					return PGRES_POLLING_FAILED;
+				}
+
+			default:
+				libpq_append_conn_error(conn, "unrecognized SSL error code: %d", err);
+				pgtls_close(conn);
+				return PGRES_POLLING_FAILED;
+		}
+	}
+
+	/* ALPN is mandatory with direct SSL connections */
+	if (conn->current_enc_method == ENC_SSL && conn->sslnegotiation[0] == 'd')
+	{
+		const unsigned char *selected;
+		unsigned int len;
+
+		SSL_get0_alpn_selected(conn->ssl, &selected, &len);
+
+		if (selected == NULL)
+		{
+			libpq_append_conn_error(conn, "direct SSL connection was established without ALPN protocol negotiation extension");
+			pgtls_close(conn);
+			return PGRES_POLLING_FAILED;
+		}
+
+		/*
+		 * We only support one protocol so that's what the negotiation should
+		 * always choose, but doesn't hurt to check.
+		 */
+		if (len != strlen(PG_ALPN_PROTOCOL) ||
+			memcmp(selected, PG_ALPN_PROTOCOL, strlen(PG_ALPN_PROTOCOL)) != 0)
+		{
+			libpq_append_conn_error(conn, "SSL connection was established with unexpected ALPN protocol");
+			pgtls_close(conn);
+			return PGRES_POLLING_FAILED;
+		}
+	}
+
+	/*
+	 * We already checked the server certificate in initialize_SSL() using
+	 * SSL_CTX_set_verify(), if root.crt exists.
+	 */
+
+	/* get server certificate */
+	conn->peer = SSL_get_peer_certificate(conn->ssl);
+	if (conn->peer == NULL)
+	{
+		char	   *err = SSLerrmessage(ERR_get_error());
+
+		libpq_append_conn_error(conn, "certificate could not be obtained: %s", err);
+		SSLerrfree(err);
+		pgtls_close(conn);
+		return PGRES_POLLING_FAILED;
+	}
+
+	if (!pq_verify_peer_name_matches_certificate(conn))
+	{
+		pgtls_close(conn);
+		return PGRES_POLLING_FAILED;
+	}
+
+	/* SSL handshake is complete */
+	return PGRES_POLLING_OK;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+	if (conn->ssl_in_use)
+	{
+		if (conn->ssl)
+		{
+			/*
+			 * We can't destroy everything SSL-related here due to the
+			 * possible later calls to OpenSSL routines which may need our
+			 * thread callbacks, so set a flag here and check at the end.
+			 */
+
+			SSL_shutdown(conn->ssl);
+			SSL_free(conn->ssl);
+			conn->ssl = NULL;
+			conn->ssl_in_use = false;
+			conn->ssl_handshake_started = false;
+		}
+
+		if (conn->peer)
+		{
+			X509_free(conn->peer);
+			conn->peer = NULL;
+		}
+
+#ifdef USE_SSL_ENGINE
+		if (conn->engine)
+		{
+			ENGINE_finish(conn->engine);
+			ENGINE_free(conn->engine);
+			conn->engine = NULL;
+		}
+#endif
+	}
+}
+
+
+/*
+ * Obtain reason string for passed SSL errcode
+ *
+ * ERR_get_error() is used by caller to get errcode to pass here.
+ * The result must be freed after use, using SSLerrfree.
+ *
+ * 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 char ssl_nomem[] = "out of memory allocating error description";
+
+#define SSL_ERR_LEN 128
+
+static char *
+SSLerrmessage(unsigned long ecode)
+{
+	const char *errreason;
+	char	   *errbuf;
+
+	errbuf = malloc(SSL_ERR_LEN);
+	if (!errbuf)
+		return ssl_nomem;
+	if (ecode == 0)
+	{
+		snprintf(errbuf, SSL_ERR_LEN, libpq_gettext("no SSL error reported"));
+		return errbuf;
+	}
+	errreason = ERR_reason_error_string(ecode);
+	if (errreason != NULL)
+	{
+		strlcpy(errbuf, errreason, SSL_ERR_LEN);
+		return errbuf;
+	}
+
+	/*
+	 * Server aborted the connection with TLS "no_application_protocol" alert.
+	 * The ERR_reason_error_string() function doesn't give any error string
+	 * for that for some reason, so do it ourselves.  See
+	 * https://github.com/openssl/openssl/issues/24300.  This is available in
+	 * OpenSSL 1.1.0 and later, as well as in LibreSSL 3.4.3 (OpenBSD 7.0) and
+	 * later.
+	 */
+#ifdef SSL_AD_NO_APPLICATION_PROTOCOL
+	if (ERR_GET_LIB(ecode) == ERR_LIB_SSL &&
+		ERR_GET_REASON(ecode) == SSL_AD_REASON_OFFSET + SSL_AD_NO_APPLICATION_PROTOCOL)
+	{
+		snprintf(errbuf, SSL_ERR_LEN, "no application protocol");
+		return errbuf;
+	}
+#endif
+
+	/* No choice but to report the numeric ecode */
+	snprintf(errbuf, SSL_ERR_LEN, libpq_gettext("SSL error code %lu"), ecode);
+	return errbuf;
+}
+
+static void
+SSLerrfree(char *buf)
+{
+	if (buf != ssl_nomem)
+		free(buf);
+}
+
+/* ------------------------------------------------------------ */
+/*					SSL information functions					*/
+/* ------------------------------------------------------------ */
+
+/*
+ *	Return pointer to OpenSSL object.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+	if (!conn)
+		return NULL;
+	return conn->ssl;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+	if (!conn)
+		return NULL;
+	if (strcmp(struct_name, "OpenSSL") == 0)
+		return conn->ssl;
+	return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+	static const char *const openssl_attrs[] = {
+		"library",
+		"key_bits",
+		"cipher",
+		"compression",
+		"protocol",
+		"alpn",
+		NULL
+	};
+	static const char *const empty_attrs[] = {NULL};
+
+	if (!conn)
+	{
+		/* Return attributes of default SSL library */
+		return openssl_attrs;
+	}
+
+	/* No attrs for unencrypted connection */
+	if (conn->ssl == NULL)
+		return empty_attrs;
+
+	return openssl_attrs;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+	if (!conn)
+	{
+		/* PQsslAttribute(NULL, "library") reports the default SSL library */
+		if (strcmp(attribute_name, "library") == 0)
+			return "OpenSSL";
+		return NULL;
+	}
+
+	/* All attributes read as NULL for a non-encrypted connection */
+	if (conn->ssl == NULL)
+		return NULL;
+
+	if (strcmp(attribute_name, "library") == 0)
+		return "OpenSSL";
+
+	if (strcmp(attribute_name, "key_bits") == 0)
+	{
+		static char sslbits_str[12];
+		int			sslbits;
+
+		SSL_get_cipher_bits(conn->ssl, &sslbits);
+		snprintf(sslbits_str, sizeof(sslbits_str), "%d", sslbits);
+		return sslbits_str;
+	}
+
+	if (strcmp(attribute_name, "cipher") == 0)
+		return SSL_get_cipher(conn->ssl);
+
+	if (strcmp(attribute_name, "compression") == 0)
+		return SSL_get_current_compression(conn->ssl) ? "on" : "off";
+
+	if (strcmp(attribute_name, "protocol") == 0)
+		return SSL_get_version(conn->ssl);
+
+	if (strcmp(attribute_name, "alpn") == 0)
+	{
+		const unsigned char *data;
+		unsigned int len;
+		static char alpn_str[256];	/* alpn doesn't support longer than 255
+									 * bytes */
+
+		SSL_get0_alpn_selected(conn->ssl, &data, &len);
+		if (data == NULL || len == 0 || len > sizeof(alpn_str) - 1)
+			return "";
+		memcpy(alpn_str, data, len);
+		alpn_str[len] = 0;
+		return alpn_str;
+	}
+
+	return NULL;				/* unknown attribute */
+}
+
+/*
+ * Private substitute BIO: this does the sending and receiving using
+ * pqsecure_raw_write() and pqsecure_raw_read() instead, to allow those
+ * functions to disable SIGPIPE and give better error messages on I/O errors.
+ *
+ * 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.
+ */
+
+/* protected by ssl_config_mutex */
+static BIO_METHOD *pgconn_bio_method_ptr;
+
+static int
+pgconn_bio_read(BIO *h, char *buf, int size)
+{
+	PGconn	   *conn = (PGconn *) BIO_get_data(h);
+	int			res;
+
+	res = pqsecure_raw_read(conn, buf, size);
+	BIO_clear_retry_flags(h);
+	conn->last_read_was_eof = res == 0;
+	if (res < 0)
+	{
+		/* If we were interrupted, tell caller to retry */
+		switch (SOCK_ERRNO)
+		{
+#ifdef EAGAIN
+			case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+			case EWOULDBLOCK:
+#endif
+			case EINTR:
+				BIO_set_retry_read(h);
+				break;
+
+			default:
+				break;
+		}
+	}
+
+	if (res > 0)
+		conn->ssl_handshake_started = true;
+
+	return res;
+}
+
+static int
+pgconn_bio_write(BIO *h, const char *buf, int size)
+{
+	int			res;
+
+	res = pqsecure_raw_write((PGconn *) BIO_get_data(h), buf, size);
+	BIO_clear_retry_flags(h);
+	if (res < 0)
+	{
+		/* If we were interrupted, tell caller to retry */
+		switch (SOCK_ERRNO)
+		{
+#ifdef EAGAIN
+			case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+			case EWOULDBLOCK:
+#endif
+			case EINTR:
+				BIO_set_retry_write(h);
+				break;
+
+			default:
+				break;
+		}
+	}
+
+	return res;
+}
+
+static long
+pgconn_bio_ctrl(BIO *h, int cmd, long num, void *ptr)
+{
+	long		res;
+	PGconn	   *conn = (PGconn *) BIO_get_data(h);
+
+	switch (cmd)
+	{
+		case BIO_CTRL_EOF:
+
+			/*
+			 * This should not be needed. pgconn_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 = conn->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 *
+pgconn_bio_method(void)
+{
+	BIO_METHOD *res;
+
+	if (pthread_mutex_lock(&ssl_config_mutex))
+		return NULL;
+
+	res = pgconn_bio_method_ptr;
+
+	if (!pgconn_bio_method_ptr)
+	{
+		int			my_bio_index;
+
+		my_bio_index = BIO_get_new_index();
+		if (my_bio_index == -1)
+			goto err;
+		my_bio_index |= BIO_TYPE_SOURCE_SINK;
+		res = BIO_meth_new(my_bio_index, "libpq socket");
+		if (!res)
+			goto err;
+
+		/*
+		 * As of this writing, these functions never fail. But check anyway,
+		 * like OpenSSL's own examples do.
+		 */
+		if (!BIO_meth_set_write(res, pgconn_bio_write) ||
+			!BIO_meth_set_read(res, pgconn_bio_read) ||
+			!BIO_meth_set_ctrl(res, pgconn_bio_ctrl))
+		{
+			goto err;
+		}
+	}
+
+	pgconn_bio_method_ptr = res;
+	pthread_mutex_unlock(&ssl_config_mutex);
+	return res;
+
+err:
+	if (res)
+		BIO_meth_free(res);
+	pthread_mutex_unlock(&ssl_config_mutex);
+	return NULL;
+}
+
+static int
+ssl_set_pgconn_bio(PGconn *conn)
+{
+	BIO		   *bio;
+	BIO_METHOD *bio_method;
+
+	bio_method = pgconn_bio_method();
+	if (bio_method == NULL)
+		return 0;
+
+	bio = BIO_new(bio_method);
+	if (bio == NULL)
+		return 0;
+
+	BIO_set_data(bio, conn);
+	BIO_set_init(bio, 1);
+
+	SSL_set_bio(conn->ssl, bio, bio);
+	return 1;
+}
+
+/*
+ * This is the default handler to return a client cert password from
+ * conn->sslpassword. Apps may install it explicitly if they want to
+ * prevent openssl from ever prompting on stdin.
+ */
+int
+PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
+{
+	if (conn && conn->sslpassword)
+	{
+		if (strlen(conn->sslpassword) + 1 > size)
+			fprintf(stderr, libpq_gettext("WARNING: sslpassword truncated\n"));
+		strncpy(buf, conn->sslpassword, size);
+		buf[size - 1] = '\0';
+		return strlen(buf);
+	}
+	else
+	{
+		buf[0] = '\0';
+		return 0;
+	}
+}
+
+PQsslKeyPassHook_OpenSSL_type
+PQgetSSLKeyPassHook_OpenSSL(void)
+{
+	return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook)
+{
+	PQsslKeyPassHook = hook;
+}
+
+/*
+ * Supply a password to decrypt a client certificate.
+ *
+ * This must match OpenSSL type pem_password_cb.
+ */
+static int
+PQssl_passwd_cb(char *buf, int size, int rwflag, void *userdata)
+{
+	PGconn	   *conn = userdata;
+
+	if (PQsslKeyPassHook)
+		return PQsslKeyPassHook(buf, size, conn);
+	else
+		return PQdefaultSSLKeyPassHook_OpenSSL(buf, size, conn);
+}
+
+/*
+ * Convert TLS protocol version string to OpenSSL values
+ *
+ * If a version is passed that is not supported by the current OpenSSL version,
+ * then we return -1. If a non-negative value is returned, subsequent code can
+ * assume it is working with a supported version.
+ *
+ * Note: this is rather similar to the backend routine in be-secure-openssl.c,
+ * so make sure to update both routines if changing this one.
+ */
+static int
+ssl_protocol_version_to_openssl(const char *protocol)
+{
+	if (pg_strcasecmp("TLSv1", protocol) == 0)
+		return TLS1_VERSION;
+
+#ifdef TLS1_1_VERSION
+	if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+		return TLS1_1_VERSION;
+#endif
+
+#ifdef TLS1_2_VERSION
+	if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+		return TLS1_2_VERSION;
+#endif
+
+#ifdef TLS1_3_VERSION
+	if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+		return TLS1_3_VERSION;
+#endif
+
+	return -1;
+}
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index c7651c98ab5..54710c13c23 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -386,15 +386,9 @@ pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
 
 	/*
 	 * 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.
+	 * algorithm to use for the result.
 	 */
-#if HAVE_X509_GET_SIGNATURE_INFO
 	if (!X509_get_signature_info(peer_cert, &algo_nid, NULL, NULL, NULL))
-#else
-	if (!OBJ_find_sigid_algs(X509_get_signature_nid(peer_cert),
-							 &algo_nid, NULL))
-#endif
 	{
 		libpq_append_conn_error(conn, "could not determine server certificate signature algorithm");
 		return NULL;
@@ -463,7 +457,6 @@ verify_cb(int ok, X509_STORE_CTX *ctx)
 	return ok;
 }
 
-#ifdef HAVE_SSL_CTX_SET_CERT_CB
 /*
  * Certificate selection callback
  *
@@ -490,7 +483,6 @@ cert_cb(SSL *ssl, void *arg)
 	 */
 	return 1;
 }
-#endif
 
 /*
  * OpenSSL-specific wrapper around
@@ -717,14 +709,11 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 /* See pqcomm.h comments on OpenSSL implementation of ALPN (RFC 7301) */
 static unsigned char alpn_protos[] = PG_ALPN_PROTOCOL_VECTOR;
 
-#ifdef HAVE_SSL_CTX_SET_KEYLOG_CALLBACK
 /*
  * SSL Key Logging callback
  *
  * This callback lets the user store all key material to a file for debugging
- * purposes.  The file will be written using the NSS keylog format.  LibreSSL
- * 3.5 introduced stub function to set the callback for OpenSSL compatibility
- * but the callback is never invoked.
+ * purposes.  The file will be written using the NSS keylog format.
  *
  * Error messages added to the connection object won't be printed anywhere if
  * the connection is successful.  Errors in processing keylogging are printed
@@ -759,7 +748,6 @@ SSL_CTX_keylog_cb(const SSL *ssl, const char *line)
 	(void) rc;					/* silence compiler warnings */
 	close(fd);
 }
-#endif
 
 /*
  *	Create per-connection SSL object, and load the client certificate,
@@ -829,10 +817,8 @@ initialize_SSL(PGconn *conn)
 		SSL_CTX_set_default_passwd_cb_userdata(SSL_context, conn);
 	}
 
-#ifdef HAVE_SSL_CTX_SET_CERT_CB
 	/* Set up a certificate selection callback. */
 	SSL_CTX_set_cert_cb(SSL_context, cert_cb, conn);
-#endif
 
 	/* Disable old protocol versions */
 	SSL_CTX_set_options(SSL_context, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
@@ -1085,12 +1071,8 @@ initialize_SSL(PGconn *conn)
 	{
 #ifdef HAVE_SSL_CTX_SET_KEYLOG_CALLBACK
 		SSL_CTX_set_keylog_callback(SSL_context, SSL_CTX_keylog_cb);
-#else
-#ifdef LIBRESSL_VERSION_NUMBER
-		fprintf(stderr, libpq_gettext("WARNING: sslkeylogfile support requires OpenSSL\n"));
 #else
 		fprintf(stderr, libpq_gettext("WARNING: libpq was not built with sslkeylogfile support\n"));
-#endif
 #endif
 	}
 
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index 1a8e2e6746e..52e4f92b980 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -473,14 +473,11 @@ PQsslAttributeNames(PGconn *conn)
 
 	return result;
 }
-#endif							/* USE_SSL */
 
 /*
  * Dummy versions of OpenSSL key password hook functions, when built without
- * OpenSSL.
+ * SSL.
  */
-#ifndef USE_OPENSSL
-
 PQsslKeyPassHook_OpenSSL_type
 PQgetSSLKeyPassHook_OpenSSL(void)
 {
@@ -498,7 +495,7 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
 {
 	return 0;
 }
-#endif							/* USE_OPENSSL */
+#endif							/* USE_SSL */
 
 /* Dummy version of GSSAPI information functions, when built without GSS support */
 #ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 3f921207a14..06276db377b 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -66,10 +66,12 @@ typedef struct
 #endif
 #endif							/* ENABLE_SSPI */
 
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_LIBRESSL)
 #include <openssl/ssl.h>
 #include <openssl/err.h>
+#endif							/* USE_OPENSSL, USE_LIBRESSL */
 
+#ifdef USE_OPENSSL
 #ifndef OPENSSL_NO_ENGINE
 #define USE_SSL_ENGINE
 #endif
@@ -627,7 +629,7 @@ struct pg_conn
 	bool		last_read_was_eof;
 
 #ifdef USE_SSL
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_LIBRESSL)
 	SSL		   *ssl;			/* SSL status, if have SSL connection */
 	X509	   *peer;			/* X509 cert of server */
 #ifdef USE_SSL_ENGINE
@@ -636,7 +638,7 @@ struct pg_conn
 	void	   *engine;			/* dummy field to keep struct the same if
 								 * OpenSSL version changes */
 #endif
-#endif							/* USE_OPENSSL */
+#endif							/* USE_OPENSSL, USE_LIBRESSL */
 #endif							/* USE_SSL */
 
 #ifdef ENABLE_GSS
diff --git a/src/interfaces/libpq/meson.build b/src/interfaces/libpq/meson.build
index b0ae72167a1..410a2d94a3f 100644
--- a/src/interfaces/libpq/meson.build
+++ b/src/interfaces/libpq/meson.build
@@ -28,7 +28,11 @@ endif
 
 if ssl.found()
   libpq_sources += files('fe-secure-common.c')
-  libpq_sources += files('fe-secure-openssl.c')
+  if ssl_library == 'openssl'
+    libpq_sources += files('fe-secure-openssl.c')
+  else
+    libpq_sources += files('fe-secure-libressl.c')
+  endif
 endif
 
 if gssapi.found()
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
index c8d8a70d896..a1bf57b808a 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -50,7 +50,7 @@
 
 
 
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_LIBRESSL)
 
 #include <openssl/rand.h>
 
@@ -134,7 +134,7 @@ pg_strong_random(void *buf, size_t len)
 	return false;
 }
 
-#else							/* not USE_OPENSSL or WIN32 */
+#else							/* not USE_OPENSSL, USE_LIBRESSL or WIN32 */
 
 /*
  * Without OpenSSL or Win32 support, just read /dev/urandom ourselves.
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index cb7c2a06193..ea38e5dd407 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -13,9 +13,9 @@ use lib $FindBin::RealBin;
 
 use SSL::Server;
 
-if ($ENV{with_ssl} ne 'openssl')
+if ($ENV{with_ssl} ne 'openssl' && $ENV{with_ssl} ne 'libressl')
 {
-	plan skip_all => 'OpenSSL not supported by this build';
+	plan skip_all => 'SSL not supported by this build';
 }
 if (!$ENV{PG_TEST_EXTRA} || $ENV{PG_TEST_EXTRA} !~ /\bssl\b/)
 {
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 88c82a4ac11..b9fb400f58b 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -16,9 +16,9 @@ use lib $FindBin::RealBin;
 
 use SSL::Server;
 
-if ($ENV{with_ssl} ne 'openssl')
+if ($ENV{with_ssl} ne 'openssl' && $ENV{with_ssl} ne 'libressl')
 {
-	plan skip_all => 'OpenSSL not supported by this build';
+	plan skip_all => 'SSL not supported by this build';
 }
 if (!$ENV{PG_TEST_EXTRA} || $ENV{PG_TEST_EXTRA} !~ /\bssl\b/)
 {
diff --git a/src/test/ssl/t/003_sslinfo.pl b/src/test/ssl/t/003_sslinfo.pl
index 56d32cbaa31..19f4277d0ad 100644
--- a/src/test/ssl/t/003_sslinfo.pl
+++ b/src/test/ssl/t/003_sslinfo.pl
@@ -14,9 +14,9 @@ use lib $FindBin::RealBin;
 
 use SSL::Server;
 
-if ($ENV{with_ssl} ne 'openssl')
+if ($ENV{with_ssl} ne 'openssl' && $ENV{with_ssl} ne 'libressl')
 {
-	plan skip_all => 'OpenSSL not supported by this build';
+	plan skip_all => 'SSL not supported by this build';
 }
 if (!$ENV{PG_TEST_EXTRA} || $ENV{PG_TEST_EXTRA} !~ /\bssl\b/)
 {
diff --git a/src/test/ssl/t/004_sni.pl b/src/test/ssl/t/004_sni.pl
index f1a135bb3c8..625d863b217 100644
--- a/src/test/ssl/t/004_sni.pl
+++ b/src/test/ssl/t/004_sni.pl
@@ -20,6 +20,11 @@ my $SERVERHOSTADDR = '127.0.0.1';
 # This is the pattern to use in pg_hba.conf to match incoming connections.
 my $SERVERHOSTCIDR = '127.0.0.1/32';
 
+if ($ENV{with_ssl} eq 'libressl')
+{
+	plan skip_all => 'SNI not supported when building with LibreSSL';
+}
+
 if ($ENV{with_ssl} ne 'openssl')
 {
 	plan skip_all => 'OpenSSL not supported by this build';
@@ -32,12 +37,6 @@ if (!$ENV{PG_TEST_EXTRA} || $ENV{PG_TEST_EXTRA} !~ /\bssl\b/)
 }
 
 my $ssl_server = SSL::Server->new();
-
-if ($ssl_server->is_libressl)
-{
-	plan skip_all => 'SNI not supported when building with LibreSSL';
-}
-
 my $node = PostgreSQL::Test::Cluster->new('primary');
 $node->init;
 
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
index 6060771c1a8..826e6116eca 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -213,6 +213,8 @@ sub get_library
 {
 	my ($self) = @_;
 
+	# XXX: Temporary hack during initial discussions of the patch
+	return 'LibreSSL' if (library_is_libressl());
 	return $self->{_library};
 }
 
@@ -230,7 +232,7 @@ sub library_is_libressl
 
 	# The HAVE_SSL_CTX_SET_CERT_CB macro isn't defined for LibreSSL.
 	# We may eventually need a less-bogus heuristic.
-	return not check_pg_config("#define HAVE_SSL_CTX_SET_CERT_CB 1");
+	return check_pg_config("#define HAVE_SSL_CTX_USE_CERTIFICATE_CHAIN_MEM 1");
 }
 
 # Internal method for copying a set of files, taking into account wildcards
diff --git a/src/test/ssl/t/SSL/Server.pm b/src/test/ssl/t/SSL/Server.pm
index 4400a432f42..faef4b51dde 100644
--- a/src/test/ssl/t/SSL/Server.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -103,6 +103,11 @@ sub new
 		$self->{flavor} = 'openssl';
 		$self->{backend} = SSL::Backend::OpenSSL->new();
 	}
+	elsif ($flavor =~ /\Alibressl\z/i)
+	{
+		$self->{flavor} = 'libressl';
+		$self->{backend} = SSL::Backend::OpenSSL->new();
+	}
 	else
 	{
 		die "SSL flavor $flavor unknown";
-- 
2.39.3 (Apple Git-146)



^ permalink  raw  reply  [nested|flat] 2+ messages in thread


end of thread, other threads:[~2026-07-09 21:14 UTC | newest]

Thread overview: 2+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2024-06-05 14:09 Re: Extension security improvement: Add support for extensions with an owned schema Marco Slot <[email protected]>
2026-07-09 21:14 LibreSSL and OpenSSL separation in libpq to support 1.1.1 deprecation Daniel Gustafsson <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox