From 7f289395ce9966230f1f90baddda89b454c7a67a Mon Sep 17 00:00:00 2001 From: Abhishek Chanda Date: Tue, 7 Jan 2025 21:54:08 -0600 Subject: [PATCH v7 1/2] Add support for dumping SSL keylog to a file This patch adds a new connection parameter which is used by libpq to write keys used in a SSL context --- doc/src/sgml/installation.sgml | 2 +- doc/src/sgml/libpq.sgml | 22 ++++++++++++++++ src/interfaces/libpq/fe-connect.c | 5 ++++ src/interfaces/libpq/fe-secure-openssl.c | 33 ++++++++++++++++++++++++ src/interfaces/libpq/libpq-int.h | 1 + src/test/perl/PostgreSQL/Test/Utils.pm | 1 + src/test/regress/pg_regress.c | 1 + src/test/ssl/t/001_ssltests.pl | 26 +++++++++++++++++++ 8 files changed, 90 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml index e076cefa3b9..39fd16651b9 100644 --- a/doc/src/sgml/installation.sgml +++ b/doc/src/sgml/installation.sgml @@ -301,7 +301,7 @@ Additionally, LibreSSL is supported using the OpenSSL compatibility layer. The minimum required version is 3.4 (from OpenBSD - version 7.0). + version 7.5). diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index 8fa0515c6a0..31b711004aa 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -1918,6 +1918,18 @@ 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 postgres + protocol using tools like wireshark. This parameter is ignored if an + SSL connection is not made. Keys are logged in the NSS format. + + + + sslpassword @@ -9170,6 +9182,16 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough) + + + + PGSSLKEYLOGFILE + + PGSSLKEYLOGFILE behaves the same as the connection parameter. + + + diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index d5051f5e820..180314ae73d 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -391,6 +391,11 @@ static const internalPQconninfoOption PQconninfoOptions[] = { "OAuth-Scope", "", 15, offsetof(struct pg_conn, oauth_scope)}, + {"sslkeylogfile", "PGSSLKEYLOGFILE", + "", 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..b0ca09b6700 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 @@ -86,6 +87,7 @@ 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); +static void SSL_CTX_keylog_cb(const SSL *ssl, const char *line); /* ------------------------------------------------------------ */ /* Procedures common to all secure sessions */ @@ -684,6 +686,34 @@ 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; +/* This is a callback that writes to a given ssl key log file */ +static void SSL_CTX_keylog_cb(const SSL *ssl, const char *line) { + int fd; + mode_t old_umask; + ssize_t bytes_written; + 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 key log file %s: %s", conn->sslkeylogfile, pg_strerror(errno)); + return; + } + + bytes_written = dprintf(fd, "%s\n", line); + if (bytes_written < 0) { + libpq_append_conn_error(conn, "could not write to ssl key log file %s: %s", conn->sslkeylogfile, pg_strerror(errno)); + close(fd); + return; + } + close(fd); +} + /* * Create per-connection SSL object, and load the client certificate, * private key, and trusted CA certs. @@ -1000,6 +1030,9 @@ initialize_SSL(PGconn *conn) } conn->ssl_in_use = true; + if (conn->sslkeylogfile && strlen(conn->sslkeylogfile) > 0) + SSL_CTX_set_keylog_callback(SSL_context, SSL_CTX_keylog_cb); + /* * 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 f36f7f19d58..174b1315b86 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -432,6 +432,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 key logs */ bool cancelRequest; /* true if this connection is used to send a * cancel request, instead of being a normal diff --git a/src/test/perl/PostgreSQL/Test/Utils.pm b/src/test/perl/PostgreSQL/Test/Utils.pm index efe0321a4ef..fedae5098aa 100644 --- a/src/test/perl/PostgreSQL/Test/Utils.pm +++ b/src/test/perl/PostgreSQL/Test/Utils.pm @@ -131,6 +131,7 @@ BEGIN PGSSLCRL PGSSLCRLDIR PGSSLKEY + PGSSLKEYLOGFILE PGSSLMAXPROTOCOLVERSION PGSSLMINPROTOCOLVERSION PGSSLMODE diff --git a/src/test/regress/pg_regress.c b/src/test/regress/pg_regress.c index 5d85dcc62f0..381a7266cb9 100644 --- a/src/test/regress/pg_regress.c +++ b/src/test/regress/pg_regress.c @@ -834,6 +834,7 @@ initialize_environment(void) unsetenv("PGSSLCRL"); unsetenv("PGSSLCRLDIR"); unsetenv("PGSSLKEY"); + unsetenv("PGSSLKEYLOGFILE"); unsetenv("PGSSLMAXPROTOCOLVERSION"); unsetenv("PGSSLMINPROTOCOLVERSION"); unsetenv("PGSSLMODE"); diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl index 5422511d4ab..ecf9bf7dd93 100644 --- a/src/test/ssl/t/001_ssltests.pl +++ b/src/test/ssl/t/001_ssltests.pl @@ -147,6 +147,32 @@ my $default_ssl_connstr = $common_connstr = "$default_ssl_connstr user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test"; +my $tempdir = PostgreSQL::Test::Utils::tempdir; + +# 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", "key log file exists"); + +# Skip permission checks on Windows/Cygwin +SKIP: +{ + skip "Permissions check not enforced on Windows", 1 + if ($windows_os || $Config::Config{osname} eq 'cygwin'); + + my $mode = (stat("$tempdir/key.txt"))[2]; + my $permissions_ok = 1; + + if ($mode & 0006) { + $permissions_ok = 0; + } + + ok($permissions_ok, "key log file is not world readble"); +} + # The server should not accept non-SSL connections. $node->connect_fails( "$common_connstr sslmode=disable", -- 2.39.3 (Apple Git-146)