diff --git a/configure b/configure
index 3c19e7e60ec..11615d1122d 100755
--- a/configure
+++ b/configure
@@ -12931,7 +12931,7 @@ 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
+ for ac_func in X509_get_signature_info SSL_CTX_set_num_tickets SSL_CTX_set_keylog_callback
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"
diff --git a/configure.ac b/configure.ac
index 65db0673f8a..debdf165044 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1382,7 +1382,7 @@ if test "$with_ssl" = openssl ; then
# 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])
+ AC_CHECK_FUNCS([X509_get_signature_info SSL_CTX_set_num_tickets SSL_CTX_set_keylog_callback])
AC_DEFINE([USE_OPENSSL], 1, [Define to 1 to build with OpenSSL support. (--with-ssl=openssl)])
elif test "$with_ssl" != no ; then
AC_MSG_ERROR([--with-ssl must specify openssl])
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index d7051190320..5e3281ca2eb 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1918,6 +1918,30 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
+
+ sslkeylogfile
+
+
+ This parameter specifies the location where libpq
+ will log keys used in this SSL context. This is useful for debugging
+ PostgreSQL protocol interactions or client
+ connections using network inspection tools like
+ Wireshark. This parameter is ignored if an
+ SSL connection is not made, or if LibreSSL
+ is used (LibreSSL does not support key
+ logging). Keys are logged using the NSS
+ format.
+
+
+ Key logging will expose potentially sensitive information in the
+ keylog file. Keylog files should be handled with the same care as
+ files.
+
+
+
+
+
+
sslpassword
diff --git a/meson.build b/meson.build
index e8b872d29ad..454ed81f5ea 100644
--- a/meson.build
+++ b/meson.build
@@ -1479,6 +1479,7 @@ if sslopt in ['auto', 'openssl']
# Function introduced in OpenSSL 1.1.1, not in LibreSSL.
['X509_get_signature_info'],
['SSL_CTX_set_num_tickets'],
+ ['SSL_CTX_set_keylog_callback'],
]
are_openssl_funcs_complete = true
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 2ac61575883..2428e4f854f 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -368,6 +368,9 @@
/* 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_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
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 715b5d5aff4..0258d9ace3c 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -401,6 +401,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"OAuth-Scope", "", 15,
offsetof(struct pg_conn, oauth_scope)},
+ {"sslkeylogfile", NULL, NULL, NULL,
+ "SSL-Key-Log-File", "", 0, /* sizeof("") = 0 */
+ offsetof(struct pg_conn, sslkeylogfile)},
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 5bb9d9779d8..4bfd8e0447c 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -57,6 +57,7 @@
* include , but some other Windows headers do.)
*/
#include "common/openssl.h"
+#include
#include
#ifdef USE_SSL_ENGINE
#include
@@ -684,6 +685,49 @@ 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.
+ */
+static void
+SSL_CTX_keylog_cb(const SSL *ssl, const char *line)
+{
+ int fd;
+ mode_t old_umask;
+ ssize_t rc;
+ PGconn *conn = SSL_get_app_data(ssl);
+
+ if (conn == NULL)
+ return;
+
+ old_umask = umask(077);
+ fd = open(conn->sslkeylogfile, O_WRONLY | O_APPEND | O_CREAT, 0600);
+ umask(old_umask);
+
+ if (fd == -1)
+ {
+ libpq_append_conn_error(conn, "could not open ssl keylog file %s: %s",
+ conn->sslkeylogfile, pg_strerror(errno));
+ return;
+ }
+
+ /* line is guaranteed by OpenSSL to be NUL terminated */
+ rc = write(fd, line, strlen(line));
+ if (rc < 0)
+ libpq_append_conn_error(conn, "could not write to ssl keylog file %s: %s",
+ conn->sslkeylogfile, pg_strerror(errno));
+ else
+ rc = write(fd, "\n", 1);
+ (void) rc; /* silence compiler warnings */
+ close(fd);
+}
+#endif
+
/*
* Create per-connection SSL object, and load the client certificate,
* private key, and trusted CA certs.
@@ -1000,6 +1044,20 @@ initialize_SSL(PGconn *conn)
}
conn->ssl_in_use = true;
+ if (conn->sslkeylogfile && strlen(conn->sslkeylogfile) > 0)
+ {
+#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
+ }
+
+
/*
* 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
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 25de3e95055..9369c217fb5 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -426,6 +426,7 @@ struct pg_conn
char *load_balance_hosts; /* load balance over hosts */
char *scram_client_key; /* base64-encoded SCRAM client key */
char *scram_server_key; /* base64-encoded SCRAM server key */
+ char *sslkeylogfile; /* where should the client write ssl keylogs */
bool cancelRequest; /* true if this connection is used to send a
* cancel request, instead of being a normal
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 5422511d4ab..086abf3b8b3 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -147,6 +147,33 @@ my $default_ssl_connstr =
$common_connstr =
"$default_ssl_connstr user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+SKIP:
+{
+ skip "Keylogging is not supported with LibreSSL", 5 if $libressl;
+
+ my $tempdir = PostgreSQL::Test::Utils::tempdir;
+ my @status;
+
+ # Properly escape backslashes in the path
+ $tempdir =~ s/\\/\\\\/g;
+
+ # Connect should work with a given sslkeylogfile
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslkeylogfile=$tempdir/key.txt sslmode=require",
+ "connect with server root cert and sslkeylogfile=$tempdir/key.txt");
+
+ # Verify the key file exists
+ ok(-f "$tempdir/key.txt", "keylog file exists at: $tempdir/key.txt");
+
+ # Skip permission checks on Windows/Cygwin
+ skip "Permissions check not enforced on Windows", 2
+ if ($windows_os || $Config::Config{osname} eq 'cygwin');
+
+ ok( (@status = stat("$tempdir/key.txt")),
+ "keylog file exists and returned status");
+ ok(@status && !($status[2] & 0006), "keylog file is not world readable");
+}
+
# The server should not accept non-SSL connections.
$node->connect_fails(
"$common_connstr sslmode=disable",