v3-0001-Add-support-for-dumping-SSL-keylog-to-a-file.patch
application/octet-stream
Filename: v3-0001-Add-support-for-dumping-SSL-keylog-to-a-file.patch
Type: application/octet-stream
Part: 0
Patch
Format: format-patch
Series: patch v3-0001
Subject: Add support for dumping SSL keylog to a file
| File | + | − |
|---|---|---|
| doc/src/sgml/libpq.sgml | 22 | 0 |
| .gitignore | 1 | 0 |
| src/interfaces/libpq/fe-connect.c | 5 | 0 |
| src/interfaces/libpq/fe-secure-openssl.c | 26 | 0 |
| src/interfaces/libpq/libpq-int.h | 1 | 0 |
| src/test/perl/PostgreSQL/Test/Utils.pm | 43 | 0 |
| src/test/regress/pg_regress.c | 1 | 0 |
| src/test/ssl/t/001_ssltests.pl | 7 | 0 |
From be9f369f20fe21b6d42f896dadf92115c987f739 Mon Sep 17 00:00:00 2001
From: Abhishek Chanda <abhishek.becs@gmail.com>
Date: Tue, 7 Jan 2025 21:54:08 -0600
Subject: [PATCH v3] 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
---
.gitignore | 1 +
doc/src/sgml/libpq.sgml | 22 ++++++++++++
src/interfaces/libpq/fe-connect.c | 5 +++
src/interfaces/libpq/fe-secure-openssl.c | 26 ++++++++++++++
src/interfaces/libpq/libpq-int.h | 1 +
src/test/perl/PostgreSQL/Test/Utils.pm | 43 ++++++++++++++++++++++++
src/test/regress/pg_regress.c | 1 +
src/test/ssl/t/001_ssltests.pl | 7 ++++
8 files changed, 106 insertions(+)
diff --git a/.gitignore b/.gitignore
index 4e911395fe..025d6115da 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,6 +31,7 @@ win32ver.rc
*.exe
lib*dll.def
lib*.pc
+./src/test/ssl/key.txt
# Local excludes in root directory
/GNUmakefile
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 105b22b317..e2d36efe21 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1909,6 +1909,18 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
</listitem>
</varlistentry>
+ <varlistentry id="libpq-connect-sslkeylogfile" xreflabel="sslkeylogfile">
+ <term><literal>sslkeylogfile</literal></term>
+ <listitem>
+ <para>
+ 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.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="libpq-connect-sslpassword" xreflabel="sslpassword">
<term><literal>sslpassword</literal></term>
<listitem>
@@ -9032,6 +9044,16 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
</para>
</listitem>
+ <listitem>
+ <para>
+ <indexterm>
+ <primary><envar>PGSSLKEYLOGFILE</envar></primary>
+ </indexterm>
+ <envar>PGSSLKEYLOGFILE</envar> behaves the same as the <xref
+ linkend="libpq-connect-sslkeylogfile"/> connection parameter.
+ </para>
+ </listitem>
+
<listitem>
<para>
<indexterm>
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 8f211821eb..19d2e03050 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -366,6 +366,11 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Load-Balance-Hosts", "", 8, /* sizeof("disable") = 8 */
offsetof(struct pg_conn, load_balance_hosts)},
+ {"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 5bb9d9779d..cbd0fe53ac 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -57,6 +57,7 @@
* 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>
@@ -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,27 @@ 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) {
+ FILE *log_file;
+ PGconn *conn = SSL_get_app_data(ssl);
+ if (conn == NULL)
+ return;
+
+ log_file = fopen(conn->sslkeylogfile, "a");
+ if (log_file == NULL) {
+ libpq_append_conn_error(conn, "could not open ssl key log file");
+ return;
+ }
+
+ if (chmod(conn->sslkeylogfile, 0600) == -1) {
+ libpq_append_conn_error(conn, "could not chmod ssl key log file");
+ return;
+ }
+ fprintf(log_file, "%s\n", line);
+ fclose(log_file);
+}
+
/*
* Create per-connection SSL object, and load the client certificate,
* private key, and trusted CA certs.
@@ -1000,6 +1023,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 4a5a7c8b5e..57b8f7e602 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -428,6 +428,7 @@ struct pg_conn
char *target_session_attrs; /* desired session properties */
char *require_auth; /* name of the expected auth method */
char *load_balance_hosts; /* load balance over hosts */
+ 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 9c83d93f79..e5b082bcf5 100644
--- a/src/test/perl/PostgreSQL/Test/Utils.pm
+++ b/src/test/perl/PostgreSQL/Test/Utils.pm
@@ -67,6 +67,7 @@ our @EXPORT = qw(
slurp_file
append_to_file
string_replace_file
+ check_file_mode
check_mode_recursive
chmod_recursive
check_pg_config
@@ -129,6 +130,7 @@ BEGIN
PGSSLCRL
PGSSLCRLDIR
PGSSLKEY
+ PGSSLKEYLOGFILE
PGSSLMAXPROTOCOLVERSION
PGSSLMINPROTOCOLVERSION
PGSSLMODE
@@ -586,6 +588,47 @@ sub string_replace_file
=pod
+=item check_file_mode(path, expected_file_mode)
+
+Check that the given file exists and mode match the expected values.
+
+=cut
+
+sub check_file_mode {
+ my ($path, $expected_file_mode) = @_;
+
+ unless (-e $path) {
+ die "Error: File '$path' does not exist\n";
+ }
+
+ my $file_stat = stat($path);
+ unless (defined($file_stat)) {
+ my $is_ENOENT = $!{ENOENT};
+ my $msg = "unable to stat $path: $!";
+ if ($is_ENOENT) {
+ warn $msg;
+ return 0;
+ }
+ else {
+ die $msg;
+ }
+ }
+
+ my $file_mode = S_IMODE($file_stat->mode);
+
+ if (S_ISREG($file_stat->mode)) {
+ if ($file_mode != $expected_file_mode) {
+ print STDERR sprintf("%s mode must be %04o\n",
+ $path, $expected_file_mode);
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+=pod
+
=item check_mode_recursive(dir, expected_dir_mode, expected_file_mode, ignore_list)
Check that all file/dir modes in a directory match the expected values,
diff --git a/src/test/regress/pg_regress.c b/src/test/regress/pg_regress.c
index cbef6d48d3..6e3dfc11af 100644
--- a/src/test/regress/pg_regress.c
+++ b/src/test/regress/pg_regress.c
@@ -830,6 +830,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 c4a8dbb001..5e4ba8dffa 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -147,6 +147,13 @@ my $default_ssl_connstr =
$common_connstr =
"$default_ssl_connstr user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+# Connect should work with a given sslkeylogfile
+$node->connect_ok(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslkeylogfile=key.txt sslmode=require",
+ "connect with server root certand sslkeylogfile=key.txt");
+
+ok(check_file_mode("key.txt", 0600), 'check key.txt mode');
+
# The server should not accept non-SSL connections.
$node->connect_fails(
"$common_connstr sslmode=disable",
--
2.47.1