Setting min/max TLS protocol in clientside libpq
Responding to the recent thread on bumping the default TLS version, I realized
that we don't have a way to set the minimum/maximum TLS protocol version in
clientside libpq. Setting the maximum protocol version obviously not terribly
important (possibly with the exception of misbehaving middle-boxes and
testing), but the minimum version can be quite useful to avoid misbehaving
and/or misconfigured servers etc.
The attached patch implements two new connection string variables for minimum
and maximum TLS protocol version, mimicking how it's done in the backend. This
does duplicate a bit of code from be-secure-openssl.c to cope with older
versions of OpenSSL, but it seemed a too trivial duplication to create
common/openssl.c (but others might disagree).
This can today be achieved by editing the local openssl configuration, but
having an override in libpq to tighten down the connection parameters make it
far easier for the user/application IMO.
cheers ./daniel
Attachments:
libpq_minmaxproto.patchapplication/octet-stream; name=libpq_minmaxproto.patch; x-unix-mode=0644Download
From 6c365125c33ecf7bac7b4fc3f795380c8a0ea782 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Sat, 30 Nov 2019 01:32:04 +0100
Subject: [PATCH] Allow setting min/max TLS protocol version in libpq
In the backend there are GUCs to control the minimum and maximum TLS
versions to allow for a connection, but the clientside libpq lacked
this ability. Disallowing servers which aren't providing secure TLS
protocols is of interest to clients, but we provide a maximum protocol
version setting by the same rationale as for the backend; to aid with
testing and to cope with misbehaving software.
---
doc/src/sgml/libpq.sgml | 65 ++++++++
src/interfaces/libpq/fe-connect.c | 8 +
src/interfaces/libpq/fe-secure-openssl.c | 189 +++++++++++++++++++++++
src/interfaces/libpq/libpq-int.h | 2 +
4 files changed, 264 insertions(+)
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 258b09cf8e..bad0d26aa3 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1635,6 +1635,33 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
</listitem>
</varlistentry>
+ <varlistentry id="libpq-connect-sslminprotocolversion" xreflabel="sslminprotocolversion">
+ <term><literal>sslminprotocolversion</literal></term>
+ <listitem>
+ <para>
+ This parameter specifies the minimum SSL/TLS protocol version to allow
+ for the connection. Valid values are <literal>TLSv1</literal>,
+ <literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
+ <literal>TLSv1.3</literal>. The supported protocols depend on the
+ version of <productname>OpenSSL</productname> used, older versions
+ doesn't support the modern protocol versions.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-connect-sslmaxprotocolversion" xreflabel="sslmaxprotocolversion">
+ <term><literal>sslmaxprotocolversion</literal></term>
+ <listitem>
+ <para>
+ This parameter specifies the maximum SSL/TLS protocol version to allow
+ for the connection. The supported values are the same as for <literal>
+ sslminprotocolversion</literal>. Setting a maximum protocol version is
+ generally only useful for testing, or in case there are software components
+ which doesn't support newer protocol versions.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="libpq-connect-krbsrvname" xreflabel="krbsrvname">
<term><literal>krbsrvname</literal></term>
<listitem>
@@ -7021,6 +7048,26 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
</para>
</listitem>
+ <listitem>
+ <para>
+ <indexterm>
+ <primary><envar>PGSSLMINPROTOCOLVERSION</envar></primary>
+ </indexterm>
+ <envar>PGSSLMINPROTOCOLVERSION</envar> behaves the same as the <xref
+ linkend="libpq-connect-sslminprotocolversion"/> connection parameter.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <indexterm>
+ <primary><envar>PGSSLMAXPROTOCOLVERSION</envar></primary>
+ </indexterm>
+ <envar>PGSSLMAXPROTOCOLVERSION</envar> behaves the same as the <xref
+ linkend="libpq-connect-sslminprotocolversion"/> connection parameter.
+ </para>
+ </listitem>
+
<listitem>
<para>
<indexterm>
@@ -7674,6 +7721,24 @@ ldap://ldap.acme.com/cn=dbserver,cn=hosts?pgconnectinfo?base?(objectclass=*)
</sect2>
+ <sect2>
+ <title>Client Protocol Usage</title>
+
+ <para>
+ When connecting using SSL, the client and server negotiate which protocol
+ to use for the connection. <productname>PostgreSQL</productname> supports
+ <literal>TLSv1</literal>, <literal>TLSv1.1</literal>, <literal>TLSv1.2</literal>
+ and <literal>TLSv1.3</literal>, but the protocols available depends on the
+ version of <productname>OpenSSL</productname> which the client is using.
+ The minimum requested version can be specified with <literal>sslminprotocolversion</literal>,
+ which will ensure that the connection use that version, or higher, or fails.
+ The maximum requested version can be specified with <literal>sslmaxprotocolversion</literal>,
+ but this is mainly only useful for testing, or in case a component doesn't
+ work with a newer protocol.
+ </para>
+
+ </sect2>
+
<sect2 id="libpq-ssl-fileusage">
<title>SSL Client File Usage</title>
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index dcd86ee804..b47498449b 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -316,6 +316,14 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Require-Peer", "", 10,
offsetof(struct pg_conn, requirepeer)},
+ {"sslminprotocolversion", "PGSSLMINPROTOCOLVERSION", NULL, NULL,
+ "SSL-Minimum-Protocol-Version", "", /* sizeof("tlsv1.x") */ 7,
+ offsetof(struct pg_conn, sslminprotocolversion)},
+
+ {"sslmaxprotocolversion", "PGSSLMAXPROTOCOLVERSION", NULL, NULL,
+ "SSL-Maximum-Protocol-Version", "", /* sizeof("tlvs1.x") */ 7,
+ offsetof(struct pg_conn, sslmaxprotocolversion)},
+
/*
* Expose gssencmode similarly to sslmode - we can still handle "disable"
* and "prefer".
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index c8dddfb5fd..9d29a9e014 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -93,6 +93,11 @@ static long win32_ssl_create_mutex = 0;
#endif
#endif /* ENABLE_THREAD_SAFETY */
+static int ssl_protocol_version_to_openssl(const char *protocol);
+#ifndef SSL_CTX_set_min_proto_version
+static int SSL_CTX_set_min_proto_version(SSL_CTX *ctx, int version);
+static int SSL_CTX_set_max_proto_version(SSL_CTX *ctx, int version);
+#endif
/* ------------------------------------------------------------ */
/* Procedures common to all secure sessions */
@@ -785,6 +790,8 @@ initialize_SSL(PGconn *conn)
bool have_cert;
bool have_rootcert;
EVP_PKEY *pkey = NULL;
+ int ssl_max_ver;
+ int ssl_min_ver;
/*
* We'll need the home directory if any of the relevant parameters are
@@ -821,6 +828,63 @@ initialize_SSL(PGconn *conn)
/* Disable old protocol versions */
SSL_CTX_set_options(SSL_context, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
+
+ if (conn->sslminprotocolversion)
+ {
+ ssl_min_ver = ssl_protocol_version_to_openssl(conn->sslminprotocolversion);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid minimum SSL version specified: %s\n"),
+ conn->sslminprotocolversion);
+ return -1;
+ }
+
+ if (!SSL_CTX_set_min_proto_version(SSL_context, ssl_min_ver))
+ {
+ char *err = SSLerrmessage(ERR_get_error());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set minimum SSL version specified: %s\n"),
+ err);
+ return -1;
+ }
+ }
+ else
+ ssl_min_ver = INT_MIN;
+
+ if (conn->sslmaxprotocolversion)
+ {
+ ssl_max_ver = ssl_protocol_version_to_openssl(conn->sslmaxprotocolversion);
+
+ if (ssl_max_ver < ssl_min_ver)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid maximum SSL version specified, must be higher than minimum SSL version: %s\n"),
+ conn->sslmaxprotocolversion);
+ return -1;
+ }
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid maximum SSL version specified: %s\n"),
+ conn->sslmaxprotocolversion);
+ return -1;
+ }
+
+ if (!SSL_CTX_set_max_proto_version(SSL_context, ssl_max_ver))
+ {
+ char *err = SSLerrmessage(ERR_get_error());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set maximum SSL version specified: %s\n"),
+ err);
+ return -1;
+ }
+ }
+
/*
* Disable OpenSSL's moving-write-buffer sanity check, because it causes
* unnecessary failures in nonblocking send cases.
@@ -1580,3 +1644,128 @@ my_SSL_set_fd(PGconn *conn, int fd)
err:
return ret;
}
+
+/*
+ * Convert TLS protocol versionstring to OpenSSL values
+ *
+ * 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.
+ */
+static int
+ssl_protocol_version_to_openssl(const char *protocol)
+{
+ if ((pg_strcasecmp("tlsv1", protocol) == 0) || pg_strcasecmp("tlsv1.0", 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;
+}
+
+/*
+ * Replacements for APIs present in newer versions of OpenSSL. This is a copy
+ * of the routines that exist in the backend.
+ */
+#ifndef SSL_CTX_set_min_proto_version
+
+/*
+ * OpenSSL versions that support TLS 1.3 shouldn't get here because they
+ * already have these functions. So we don't have to keep updating the below
+ * code for every new TLS version, and eventually it can go away. But let's
+ * just check this to make sure ...
+ */
+#ifdef TLS1_3_VERSION
+#error OpenSSL version mismatch
+#endif
+
+static int
+SSL_CTX_set_min_proto_version(SSL_CTX *ctx, int version)
+{
+ int ssl_options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
+
+ if (version > TLS1_VERSION)
+ ssl_options |= SSL_OP_NO_TLSv1;
+ /*
+ * Some OpenSSL versions define TLS*_VERSION macros but not the
+ * corresponding SSL_OP_NO_* macro, so in those cases we have to return
+ * unsuccessfully here.
+ */
+#ifdef TLS1_1_VERSION
+ if (version > TLS1_1_VERSION)
+ {
+#ifdef SSL_OP_NO_TLSv1_1
+ ssl_options |= SSL_OP_NO_TLSv1_1;
+#else
+ return 0;
+#endif
+ }
+#endif
+#ifdef TLS1_2_VERSION
+ if (version > TLS1_2_VERSION)
+ {
+#ifdef SSL_OP_NO_TLSv1_2
+ ssl_options |= SSL_OP_NO_TLSv1_2;
+#else
+ return 0;
+#endif
+ }
+#endif
+
+ SSL_CTX_set_options(ctx, ssl_options);
+
+ return 1; /* success */
+}
+
+static int
+SSL_CTX_set_max_proto_version(SSL_CTX *ctx, int version)
+{
+ int ssl_options = 0;
+
+ AssertArg(version != 0);
+
+ /*
+ * Some OpenSSL versions define TLS*_VERSION macros but not the
+ * corresponding SSL_OP_NO_* macro, so in those cases we have to return
+ * unsuccessfully here.
+ */
+#ifdef TLS1_1_VERSION
+ if (version < TLS1_1_VERSION)
+ {
+#ifdef SSL_OP_NO_TLSv1_1
+ ssl_options |= SSL_OP_NO_TLSv1_1;
+#else
+ return 0;
+#endif
+ }
+#endif
+#ifdef TLS1_2_VERSION
+ if (version < TLS1_2_VERSION)
+ {
+#ifdef SSL_OP_NO_TLSv1_2
+ ssl_options |= SSL_OP_NO_TLSv1_2;
+#else
+ return 0;
+#endif
+ }
+#endif
+
+ SSL_CTX_set_options(ctx, ssl_options);
+
+ return 1; /* success */
+}
+
+#endif /* !SSL_CTX_set_min_proto_version */
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 64468ab4da..cfe9e86471 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -362,6 +362,8 @@ struct pg_conn
char *sslrootcert; /* root certificate filename */
char *sslcrl; /* certificate revocation list filename */
char *requirepeer; /* required peer credentials for local sockets */
+ char *sslminprotocolversion; /* minimum TLS protocol version */
+ char *sslmaxprotocolversion; /* maximum TLS protocol version */
#if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
char *krbsrvname; /* Kerberos service name */
--
2.21.0 (Apple Git-122.2)
Hello,
On 2019/12/04 2:37, Daniel Gustafsson wrote:
The attached patch implements two new connection string variables for minimum
and maximum TLS protocol version, mimicking how it's done in the backend. This
does duplicate a bit of code from be-secure-openssl.c to cope with older
versions of OpenSSL, but it seemed a too trivial duplication to create
common/openssl.c (but others might disagree).
I've looked at the patch and I have a couple comments.
+ if (ssl_max_ver < ssl_min_ver) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("invalid maximum SSL version specified, must be higher than minimum SSL version: %s\n"), + conn->sslmaxprotocolversion); + return -1; + } + + if (ssl_max_ver == -1) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("invalid maximum SSL version specified: %s\n"), + conn->sslmaxprotocolversion); + return -1; + }
I think we should raise the error "invalid maximum SSL version
specified" earlier. If ssl_protocol_version_to_openssl() returns -1 and
ssl_min_ver is valid we never reach the condition "ssl_max_ver == -1".
Also it might confuse users to get the error "invalid maximum SSL
version specified, must be higher than minimum SSL version" instead of
former one.
Secondly I think the error "invalid maximum SSL version specified"
itself might confuse users, in the case if the input is good but a build
doesn't support desired version. So I think it is better to do two
checks here: check for a correct input and check if a build supports it.
In the second case we may raise "SSL version %s not supported by this
build". It is actually like backend does: guc.c checks for correct input
using ssl_protocol_versions_info and ssl_protocol_version_to_openssl()
checks if a build supports the version.
--
Arthur
The following review has been posted through the commitfest application:
make installcheck-world: tested, passed
Implements feature: tested, passed
Spec compliant: not tested
Documentation: tested, passed
Hello
I have applied the patch and did some basic testing with various combination of min and max TLS protocol versions. Overall the functionality works and the chosen TLS protocol aligns with the min/max TLS protocol settings on the PG server side.
I agree with Arthur that it makes sense to check the validity of "conn->sslmaxprotocolversion" first before checking if it is larger than "conn->sslminprotocolversion"
A small suggestion here. I see that PG server defaults TLS min version to be TLSv1.2 and max version to none. So by default the server accepts TLSv1.2 and above. I think on the client side, it also makes sense to have the same defaults as the server. In the patch, if the client does not supply "sslminprotocolversion", it will run to "else" statement and sets TLS min version to "INT_MIN", which is a huge negative number and of course openssl won't set it. I think this else statement can be enhanced a little to set "sslminprotocolversion" to TLSv1.2 by default to match the server and provide some warning message that TLS minimum has defaulted to TLSv1.2.
Cary
HighGo Software Canada
On Thu, Jan 02, 2020 at 09:46:44PM +0000, cary huang wrote:
I agree with Arthur that it makes sense to check the validity of
"conn->sslmaxprotocolversion" first before checking if it is larger
than "conn->sslminprotocolversion"
Here I don't agree. Why not just let OpenSSL handle things with
SSL_CTX_set_min_proto_version? We don't bother about that in the
backend code for that reason on top of keeping the code more simple
with less error handling. And things are cleaner when it comes to
this libpq patch by giving up with the INT_MIN hack.
A small suggestion here. I see that PG server defaults TLS min
version to be TLSv1.2 and max version to none. So by default the
server accepts TLSv1.2 and above. I think on the client side, it
also makes sense to have the same defaults as the server.
Yeah, that makes sense. Even more now that I have just removed
support for OpenSSL 0.9.8 and 1.0.0 ;)
There could be an argument to lower down the default if we count for
backends built with OpenSSL versions older than libpq, but I am not
ready to buy that there would be many of those.
In the patch, if the client does not supply "sslminprotocolversion",
it will run to "else" statement and sets TLS min version to "INT_MIN",
which is a huge negative number and of course openssl won't set
it. I think this else statement can be enhanced a little to set
"sslminprotocolversion" to TLSv1.2 by default to match the server
and provide some warning message that TLS minimum has defaulted to
TLSv1.2.
In this patch fe-secure-openssl.c has just done a copy-paste of
SSL_CTX_set_min_proto_version and SSL_CTX_set_max_proto_version
present in be-secure-openssl.c. That's not good. Could you refactor
that please as a separate file? For example openssl-protocol.c in
src/common/? src/common/ stuff is built with -fPIC since 7143b3e so
there is no need to include directly the source files in the
Makefile. A shame you cannot do that for
ssl_protocol_version_to_openssl(), so for that a note would be welcome
on top of the former backend routine and the one you are adding.
The patch has conflicts with libpq-int.h as far as I can see. That
should be easy enough to solve.
The patch should have tests in src/test/ssl/, like for invalid values,
incorrect combinations leading to failures, etc.
--
Michael
On Mon, Jan 6, 2020 at 7:02 AM Michael Paquier <michael@paquier.xyz> wrote:
On Thu, Jan 02, 2020 at 09:46:44PM +0000, cary huang wrote:
I agree with Arthur that it makes sense to check the validity of
"conn->sslmaxprotocolversion" first before checking if it is larger
than "conn->sslminprotocolversion"Here I don't agree. Why not just let OpenSSL handle things with
SSL_CTX_set_min_proto_version? We don't bother about that in the
backend code for that reason on top of keeping the code more simple
with less error handling. And things are cleaner when it comes to
this libpq patch by giving up with the INT_MIN hack.A small suggestion here. I see that PG server defaults TLS min
version to be TLSv1.2 and max version to none. So by default the
server accepts TLSv1.2 and above. I think on the client side, it
also makes sense to have the same defaults as the server.Yeah, that makes sense. Even more now that I have just removed
support for OpenSSL 0.9.8 and 1.0.0 ;)There could be an argument to lower down the default if we count for
backends built with OpenSSL versions older than libpq, but I am not
ready to buy that there would be many of those.
Not having thought about it in much detail, but it's a fairly common
scenario to have a much newer version of libpq (and the platform it's
built on) than the server. E.g. a v12 libpq against a v9.6 postgres
server is very common. For example, debian based systems will
auto-upgrade your libpq, but not your server (for obvious reasons).
And it's also quite common to upgrade platforms for the application
much more frequently than the database server platform.
--
Magnus Hagander
Me: https://www.hagander.net/
Work: https://www.redpill-linpro.com/
Magnus Hagander <magnus@hagander.net> writes:
Not having thought about it in much detail, but it's a fairly common
scenario to have a much newer version of libpq (and the platform it's
built on) than the server. E.g. a v12 libpq against a v9.6 postgres
server is very common. For example, debian based systems will
auto-upgrade your libpq, but not your server (for obvious reasons).
And it's also quite common to upgrade platforms for the application
much more frequently than the database server platform.
Yeah, there's a reason why we expect pg_dump and psql to function with
ancient server versions. We shouldn't break this scenario with
careless rejiggering of libpq's connection defaults.
regards, tom lane
On Mon, Jan 06, 2020 at 09:37:54AM -0500, Tom Lane wrote:
Magnus Hagander <magnus@hagander.net> writes:
Not having thought about it in much detail, but it's a fairly common
scenario to have a much newer version of libpq (and the platform it's
built on) than the server. E.g. a v12 libpq against a v9.6 postgres
server is very common. For example, debian based systems will
auto-upgrade your libpq, but not your server (for obvious reasons).
And it's also quite common to upgrade platforms for the application
much more frequently than the database server platform.Yeah, there's a reason why we expect pg_dump and psql to function with
ancient server versions. We shouldn't break this scenario with
careless rejiggering of libpq's connection defaults.
Good points. Let's not do that then.
--
Michael
Thanks for review everyone! A v2 of the patch which I believe addresses all
concerns raised is attached.
On 6 Jan 2020, at 07:01, Michael Paquier <michael@paquier.xyz> wrote:
On Thu, Jan 02, 2020 at 09:46:44PM +0000, cary huang wrote:
I agree with Arthur that it makes sense to check the validity of
"conn->sslmaxprotocolversion" first before checking if it is larger
than "conn->sslminprotocolversion"Here I don't agree. Why not just let OpenSSL handle things with
SSL_CTX_set_min_proto_version? We don't bother about that in the
backend code for that reason on top of keeping the code more simple
with less error handling. And things are cleaner when it comes to
this libpq patch by giving up with the INT_MIN hack.
I looked into this and it turns out that OpenSSL does nothing to prevent the
caller from setting a nonsensical protocol range like min=tlsv1.3,max=tlsv1.1.
Thus, it's quite easy to screw up the backend server config and get it to start
properly, but with quite unrelated error messages as a result on connection.
Since I think this needs to be dealt with for both backend and frontend (if
this is accepted), I removed it from this patch to return to it in a separate
thread.
In the patch, if the client does not supply "sslminprotocolversion",
it will run to "else" statement and sets TLS min version to "INT_MIN",
which is a huge negative number and of course openssl won't set
it. I think this else statement can be enhanced a little to set
"sslminprotocolversion" to TLSv1.2 by default to match the server
and provide some warning message that TLS minimum has defaulted to
TLSv1.2.In this patch fe-secure-openssl.c has just done a copy-paste of
SSL_CTX_set_min_proto_version and SSL_CTX_set_max_proto_version
present in be-secure-openssl.c. That's not good. Could you refactor
that please as a separate file?
Done. I opted for a more generic header to make usage of the code easier, not
sure if thats ok.
One thing I noticed when looking at it is that we now have sha2_openssl.c and
openssl_protocol.c in src/common. For easier visual grouping of OpenSSL
functionality, it makes sense to me to rename sha2_openssl.c to openssl_sha2.c,
but that might just be pointless churn.
The patch should have tests in src/test/ssl/, like for invalid values,
incorrect combinations leading to failures, etc.
Also done.
cheers ./daniel
Attachments:
libpq_minmaxproto_v2.patchapplication/octet-stream; name=libpq_minmaxproto_v2.patch; x-unix-mode=0644Download
From 2f280935fb300c48a5082191d4e51b5ed1239283 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Sat, 30 Nov 2019 01:32:04 +0100
Subject: [PATCH] Allow setting min/max TLS protocol version in libpq
In the backend there are GUCs to control the minimum and maximum TLS
versions to allow for a connection, but the clientside libpq lacked
this ability. Disallowing servers which aren't providing secure TLS
protocols is of interest to clients, but we provide a maximum protocol
version setting by the same rationale as for the backend; to aid with
testing and to cope with misbehaving software.
This refactors the OpenSSL replacement functions for setting TLS
version from the backend to src/common to avoid code duplication.
---
doc/src/sgml/libpq.sgml | 65 +++++++++++++
src/backend/libpq/be-secure-openssl.c | 99 +------------------
src/common/Makefile | 3 +-
src/common/openssl_protocol.c | 115 +++++++++++++++++++++++
src/include/common/openssl.h | 27 ++++++
src/interfaces/libpq/fe-connect.c | 8 ++
src/interfaces/libpq/fe-secure-openssl.c | 81 ++++++++++++++++
src/interfaces/libpq/libpq-int.h | 2 +
src/test/ssl/t/001_ssltests.pl | 14 ++-
9 files changed, 314 insertions(+), 100 deletions(-)
create mode 100644 src/common/openssl_protocol.c
create mode 100644 src/include/common/openssl.h
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 64cff49c4d..5c7816ce6f 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1727,6 +1727,33 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
</listitem>
</varlistentry>
+ <varlistentry id="libpq-connect-sslminprotocolversion" xreflabel="sslminprotocolversion">
+ <term><literal>sslminprotocolversion</literal></term>
+ <listitem>
+ <para>
+ This parameter specifies the minimum SSL/TLS protocol version to allow
+ for the connection. Valid values are <literal>TLSv1</literal>,
+ <literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
+ <literal>TLSv1.3</literal>. The supported protocols depend on the
+ version of <productname>OpenSSL</productname> used, older versions
+ doesn't support the modern protocol versions.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-connect-sslmaxprotocolversion" xreflabel="sslmaxprotocolversion">
+ <term><literal>sslmaxprotocolversion</literal></term>
+ <listitem>
+ <para>
+ This parameter specifies the maximum SSL/TLS protocol version to allow
+ for the connection. The supported values are the same as for <literal>
+ sslminprotocolversion</literal>. Setting a maximum protocol version is
+ generally only useful for testing, or in case there are software components
+ which doesn't support newer protocol versions.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="libpq-connect-krbsrvname" xreflabel="krbsrvname">
<term><literal>krbsrvname</literal></term>
<listitem>
@@ -7115,6 +7142,26 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
</para>
</listitem>
+ <listitem>
+ <para>
+ <indexterm>
+ <primary><envar>PGSSLMINPROTOCOLVERSION</envar></primary>
+ </indexterm>
+ <envar>PGSSLMINPROTOCOLVERSION</envar> behaves the same as the <xref
+ linkend="libpq-connect-sslminprotocolversion"/> connection parameter.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <indexterm>
+ <primary><envar>PGSSLMAXPROTOCOLVERSION</envar></primary>
+ </indexterm>
+ <envar>PGSSLMAXPROTOCOLVERSION</envar> behaves the same as the <xref
+ linkend="libpq-connect-sslminprotocolversion"/> connection parameter.
+ </para>
+ </listitem>
+
<listitem>
<para>
<indexterm>
@@ -7788,6 +7835,24 @@ ldap://ldap.acme.com/cn=dbserver,cn=hosts?pgconnectinfo?base?(objectclass=*)
</sect2>
+ <sect2>
+ <title>Client Protocol Usage</title>
+
+ <para>
+ When connecting using SSL, the client and server negotiate which protocol
+ to use for the connection. <productname>PostgreSQL</productname> supports
+ <literal>TLSv1</literal>, <literal>TLSv1.1</literal>, <literal>TLSv1.2</literal>
+ and <literal>TLSv1.3</literal>, but the protocols available depends on the
+ version of <productname>OpenSSL</productname> which the client is using.
+ The minimum requested version can be specified with <literal>sslminprotocolversion</literal>,
+ which will ensure that the connection use that version, or higher, or fails.
+ The maximum requested version can be specified with <literal>sslmaxprotocolversion</literal>,
+ but this is mainly only useful for testing, or in case a component doesn't
+ work with a newer protocol.
+ </para>
+
+ </sect2>
+
<sect2 id="libpq-ssl-fileusage">
<title>SSL Client File Usage</title>
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 62f1fcab2b..0cc59f1be1 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -36,6 +36,7 @@
#include <openssl/ec.h>
#endif
+#include "common/openssl.h"
#include "libpq/libpq.h"
#include "miscadmin.h"
#include "pgstat.h"
@@ -69,11 +70,6 @@ static bool ssl_is_server_start;
static int ssl_protocol_version_to_openssl(int v, const char *guc_name,
int loglevel);
-#ifndef SSL_CTX_set_min_proto_version
-static int SSL_CTX_set_min_proto_version(SSL_CTX *ctx, int version);
-static int SSL_CTX_set_max_proto_version(SSL_CTX *ctx, int version);
-#endif
-
/* ------------------------------------------------------------ */
/* Public interface */
@@ -1314,96 +1310,3 @@ ssl_protocol_version_to_openssl(int v, const char *guc_name, int loglevel)
GetConfigOption(guc_name, false, false))));
return -1;
}
-
-/*
- * Replacements for APIs present in newer versions of OpenSSL
- */
-#ifndef SSL_CTX_set_min_proto_version
-
-/*
- * OpenSSL versions that support TLS 1.3 shouldn't get here because they
- * already have these functions. So we don't have to keep updating the below
- * code for every new TLS version, and eventually it can go away. But let's
- * just check this to make sure ...
- */
-#ifdef TLS1_3_VERSION
-#error OpenSSL version mismatch
-#endif
-
-static int
-SSL_CTX_set_min_proto_version(SSL_CTX *ctx, int version)
-{
- int ssl_options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
-
- if (version > TLS1_VERSION)
- ssl_options |= SSL_OP_NO_TLSv1;
- /*
- * Some OpenSSL versions define TLS*_VERSION macros but not the
- * corresponding SSL_OP_NO_* macro, so in those cases we have to return
- * unsuccessfully here.
- */
-#ifdef TLS1_1_VERSION
- if (version > TLS1_1_VERSION)
- {
-#ifdef SSL_OP_NO_TLSv1_1
- ssl_options |= SSL_OP_NO_TLSv1_1;
-#else
- return 0;
-#endif
- }
-#endif
-#ifdef TLS1_2_VERSION
- if (version > TLS1_2_VERSION)
- {
-#ifdef SSL_OP_NO_TLSv1_2
- ssl_options |= SSL_OP_NO_TLSv1_2;
-#else
- return 0;
-#endif
- }
-#endif
-
- SSL_CTX_set_options(ctx, ssl_options);
-
- return 1; /* success */
-}
-
-static int
-SSL_CTX_set_max_proto_version(SSL_CTX *ctx, int version)
-{
- int ssl_options = 0;
-
- AssertArg(version != 0);
-
- /*
- * Some OpenSSL versions define TLS*_VERSION macros but not the
- * corresponding SSL_OP_NO_* macro, so in those cases we have to return
- * unsuccessfully here.
- */
-#ifdef TLS1_1_VERSION
- if (version < TLS1_1_VERSION)
- {
-#ifdef SSL_OP_NO_TLSv1_1
- ssl_options |= SSL_OP_NO_TLSv1_1;
-#else
- return 0;
-#endif
- }
-#endif
-#ifdef TLS1_2_VERSION
- if (version < TLS1_2_VERSION)
- {
-#ifdef SSL_OP_NO_TLSv1_2
- ssl_options |= SSL_OP_NO_TLSv1_2;
-#else
- return 0;
-#endif
- }
-#endif
-
- SSL_CTX_set_options(ctx, ssl_options);
-
- return 1; /* success */
-}
-
-#endif /* !SSL_CTX_set_min_proto_version */
diff --git a/src/common/Makefile b/src/common/Makefile
index ffb0f6edff..142e6edfbb 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -73,7 +73,8 @@ OBJS_COMMON = \
wait_error.o
ifeq ($(with_openssl),yes)
-OBJS_COMMON += sha2_openssl.o
+OBJS_COMMON += sha2_openssl.o \
+ openssl_protocol.o
else
OBJS_COMMON += sha2.o
endif
diff --git a/src/common/openssl_protocol.c b/src/common/openssl_protocol.c
new file mode 100644
index 0000000000..eef86b8a68
--- /dev/null
+++ b/src/common/openssl_protocol.c
@@ -0,0 +1,115 @@
+/*-------------------------------------------------------------------------
+ *
+ * openssl_protocol.c
+ * OpenSSL functionality shared between frontend and backend
+ *
+ * This should only be used if code is compiled with OpenSSL support.
+ *
+ * Portions Copyright (c) 2018-2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/openssl_protocol.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include <openssl/ssl.h>
+
+/*
+ * Replacements for APIs present in newer versions of OpenSSL
+ */
+#ifndef SSL_CTX_set_min_proto_version
+
+/*
+ * OpenSSL versions that support TLS 1.3 shouldn't get here because they
+ * already have these functions. So we don't have to keep updating the below
+ * code for every new TLS version, and eventually it can go away. But let's
+ * just check this to make sure ...
+ */
+#ifdef TLS1_3_VERSION
+#error OpenSSL version mismatch
+#endif
+
+static int
+SSL_CTX_set_min_proto_version(SSL_CTX *ctx, int version)
+{
+ int ssl_options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
+
+ if (version > TLS1_VERSION)
+ ssl_options |= SSL_OP_NO_TLSv1;
+ /*
+ * Some OpenSSL versions define TLS*_VERSION macros but not the
+ * corresponding SSL_OP_NO_* macro, so in those cases we have to return
+ * unsuccessfully here.
+ */
+#ifdef TLS1_1_VERSION
+ if (version > TLS1_1_VERSION)
+ {
+#ifdef SSL_OP_NO_TLSv1_1
+ ssl_options |= SSL_OP_NO_TLSv1_1;
+#else
+ return 0;
+#endif
+ }
+#endif
+#ifdef TLS1_2_VERSION
+ if (version > TLS1_2_VERSION)
+ {
+#ifdef SSL_OP_NO_TLSv1_2
+ ssl_options |= SSL_OP_NO_TLSv1_2;
+#else
+ return 0;
+#endif
+ }
+#endif
+
+ SSL_CTX_set_options(ctx, ssl_options);
+
+ return 1; /* success */
+}
+
+static int
+SSL_CTX_set_max_proto_version(SSL_CTX *ctx, int version)
+{
+ int ssl_options = 0;
+
+ AssertArg(version != 0);
+
+ /*
+ * Some OpenSSL versions define TLS*_VERSION macros but not the
+ * corresponding SSL_OP_NO_* macro, so in those cases we have to return
+ * unsuccessfully here.
+ */
+#ifdef TLS1_1_VERSION
+ if (version < TLS1_1_VERSION)
+ {
+#ifdef SSL_OP_NO_TLSv1_1
+ ssl_options |= SSL_OP_NO_TLSv1_1;
+#else
+ return 0;
+#endif
+ }
+#endif
+#ifdef TLS1_2_VERSION
+ if (version < TLS1_2_VERSION)
+ {
+#ifdef SSL_OP_NO_TLSv1_2
+ ssl_options |= SSL_OP_NO_TLSv1_2;
+#else
+ return 0;
+#endif
+ }
+#endif
+
+ SSL_CTX_set_options(ctx, ssl_options);
+
+ return 1; /* success */
+}
+
+#endif /* !SSL_CTX_set_min_proto_version */
diff --git a/src/include/common/openssl.h b/src/include/common/openssl.h
new file mode 100644
index 0000000000..b1aee24ee8
--- /dev/null
+++ b/src/include/common/openssl.h
@@ -0,0 +1,27 @@
+/*-------------------------------------------------------------------------
+ *
+ * openssl.h
+ * OpenSSL supporting functionality shared between frontend and backend
+ *
+ * Portions Copyright (c) 2018-2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/openssl.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _OPENSSL_H_
+#define _OPENSSL_H_
+
+#ifdef USE_OPENSSL
+#include <openssl/ssl.h>
+
+/* src/common/openssl_protocol.c */
+#ifndef SSL_CTX_set_min_proto_version
+static int SSL_CTX_set_min_proto_version(SSL_CTX *ctx, int version);
+static int SSL_CTX_set_max_proto_version(SSL_CTX *ctx, int version);
+#endif
+
+#endif
+
+#endif /* _OPENSSL_H_ */
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 89b134665b..91d7ced2fd 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -320,6 +320,14 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Require-Peer", "", 10,
offsetof(struct pg_conn, requirepeer)},
+ {"sslminprotocolversion", "PGSSLMINPROTOCOLVERSION", NULL, NULL,
+ "SSL-Minimum-Protocol-Version", "", /* sizeof("tlsv1.x") */ 7,
+ offsetof(struct pg_conn, sslminprotocolversion)},
+
+ {"sslmaxprotocolversion", "PGSSLMAXPROTOCOLVERSION", NULL, NULL,
+ "SSL-Maximum-Protocol-Version", "", /* sizeof("tlvs1.x") */ 7,
+ offsetof(struct pg_conn, sslmaxprotocolversion)},
+
/*
* As with SSL, all GSS options are exposed even in builds that don't have
* support.
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 0e84fc8ac6..f20d8fa287 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -30,6 +30,7 @@
#include "fe-auth.h"
#include "fe-secure-common.h"
#include "libpq-int.h"
+#include "common/openssl.h"
#ifdef WIN32
#include "win32.h"
@@ -95,6 +96,7 @@ static long win32_ssl_create_mutex = 0;
#endif /* ENABLE_THREAD_SAFETY */
static PQsslKeyPassHook_type PQsslKeyPassHook = NULL;
+static int ssl_protocol_version_to_openssl(const char *protocol);
/* ------------------------------------------------------------ */
/* Procedures common to all secure sessions */
@@ -787,6 +789,8 @@ initialize_SSL(PGconn *conn)
bool have_cert;
bool have_rootcert;
EVP_PKEY *pkey = NULL;
+ int ssl_max_ver;
+ int ssl_min_ver;
/*
* We'll need the home directory if any of the relevant parameters are
@@ -843,6 +847,52 @@ initialize_SSL(PGconn *conn)
/* Disable old protocol versions */
SSL_CTX_set_options(SSL_context, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
+ if (conn->sslminprotocolversion)
+ {
+ ssl_min_ver = ssl_protocol_version_to_openssl(conn->sslminprotocolversion);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid minimum protocol version specified: %s\n"),
+ conn->sslminprotocolversion);
+ return -1;
+ }
+
+ if (!SSL_CTX_set_min_proto_version(SSL_context, ssl_min_ver))
+ {
+ char *err = SSLerrmessage(ERR_get_error());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set minimum protocol version specified: %s\n"),
+ err);
+ return -1;
+ }
+ }
+
+ if (conn->sslmaxprotocolversion)
+ {
+ ssl_max_ver = ssl_protocol_version_to_openssl(conn->sslmaxprotocolversion);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid or unsupported maximum protocol version specified: %s\n"),
+ conn->sslmaxprotocolversion);
+ return -1;
+ }
+
+ if (!SSL_CTX_set_max_proto_version(SSL_context, ssl_max_ver))
+ {
+ char *err = SSLerrmessage(ERR_get_error());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set maximum SSL version specified: %s\n"),
+ err);
+ return -1;
+ }
+ }
+
/*
* Disable OpenSSL's moving-write-buffer sanity check, because it causes
* unnecessary failures in nonblocking send cases.
@@ -1659,3 +1709,34 @@ PQssl_passwd_cb(char *buf, int size, int rwflag, void *userdata)
else
return PQdefaultSSLKeyPassHook(buf, size, conn);
}
+
+/*
+ * Convert TLS protocol versionstring to OpenSSL values
+ *
+ * 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.
+ */
+static int
+ssl_protocol_version_to_openssl(const char *protocol)
+{
+ if ((pg_strcasecmp("tlsv1", protocol) == 0) || pg_strcasecmp("tlsv1.0", 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/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 1e36be20bf..97a4d7246d 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -367,6 +367,8 @@ struct pg_conn
char *krbsrvname; /* Kerberos service name */
char *gsslib; /* What GSS library to use ("gssapi" or
* "sspi") */
+ char *sslminprotocolversion; /* minimum TLS protocol version */
+ char *sslmaxprotocolversion; /* maximum TLS protocol version */
/* Type of connection to make. Possible values: any, read-write. */
char *target_session_attrs;
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 83fcd5e839..e7726bccfe 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -13,7 +13,7 @@ use SSLServer;
if ($ENV{with_openssl} eq 'yes')
{
- plan tests => 84;
+ plan tests => 87;
}
else
{
@@ -338,6 +338,18 @@ command_like(
^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,_null_,_null_,_null_\r?$}mx,
'pg_stat_ssl view without client certificate');
+# Test min/mix protocol versions
+test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require sslminprotocolversion=tlsv1.2 sslmaxprotocolversion=tlsv1.3",
+ "connect with correct range of allowed TLS protocol versions");
+
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require sslminprotocolversion=tlsv1.3 sslmaxprotocolversion=tlsv1.2",
+ qr/SSL error/,
+ "connect with an incorrect range of TLS protocol versions leaving no versions allowed");
+
### Server-side tests.
###
### Test certificate authorization.
--
2.21.0 (Apple Git-122.2)
On Fri, Jan 10, 2020 at 12:01:36AM +0100, Daniel Gustafsson wrote:
I looked into this and it turns out that OpenSSL does nothing to prevent the
caller from setting a nonsensical protocol range like min=tlsv1.3,max=tlsv1.1.
Thus, it's quite easy to screw up the backend server config and get it to start
properly, but with quite unrelated error messages as a result on connection.
FWIW, here is the error produced, and that's confusing:
$ psql -d "host=localhost sslmode=require"
psql: error: could not connect to server: SSL error: tlsv1 alert
internal error
Since I think this needs to be dealt with for both backend and frontend (if
this is accepted), I removed it from this patch to return to it in a separate
thread.
HEAD and back branches only care about the backend, so I think that we
should address this part first as your patch would I guess reuse the
interface we finish by using for the backend. Looking at OpenSSL, I
agree that there is no internal logic to perform sanity checks on the
min/max bounds. Still I can see that OpenSSL 1.1.0 has added some
"get" routines for SSL_CTX_set_min/max_proto_version:
https://www.openssl.org/docs/man1.1.0/man3/SSL_CTX_set_min_proto_version.html
Hmmmmeuh. It would be perfect to rely only on OpenSSL for that part
to bring some sanity, and compare the results fetched from the SSL
context so as we don't have to worry about special cases in with the
GUC reload if the parameter is not set, or the parameter value is not
supported. Now, OpenSSL <= 1.0.2 cannot do that, and you can get the
values set only after doing the set, so adding the compatibility
argument it is much more tempting to use our
ssl_protocol_version_to_openssl() wrapper and complain iff:
- both the min and max are supported values.
- min/max are incompatible.
And the check needs to be done before attempting to set the min/max
protos so as you don't finish with an incorrect intermediate state.
Daniel, are you planning to start a new thread?
One thing I noticed when looking at it is that we now have sha2_openssl.c and
openssl_protocol.c in src/common. For easier visual grouping of OpenSSL
functionality, it makes sense to me to rename sha2_openssl.c to openssl_sha2.c,
but that might just be pointless churn.
Databases like consistency, and so do I, so no issues from me to do a
rename of the sha2.c file. That makes sense with the addition of the
new file.
--
Michael
On 11 Jan 2020, at 03:49, Michael Paquier <michael@paquier.xyz> wrote:
Hmmmmeuh. It would be perfect to rely only on OpenSSL for that part
to bring some sanity, and compare the results fetched from the SSL
context so as we don't have to worry about special cases in with the
GUC reload if the parameter is not set, or the parameter value is not
supported.
I'm not convinced about this, but since there is a thread opened for discussing
the range check let's take it over there.
Daniel, are you planning to start a new thread?
I was going to, but you beat me to it =)
One thing I noticed when looking at it is that we now have sha2_openssl.c and
openssl_protocol.c in src/common. For easier visual grouping of OpenSSL
functionality, it makes sense to me to rename sha2_openssl.c to openssl_sha2.c,
but that might just be pointless churn.Databases like consistency, and so do I, so no issues from me to do a
rename of the sha2.c file. That makes sense with the addition of the
new file.
Done in the attached v3.
cheers ./daniel
Attachments:
libpq_minmaxproto_v3.patchapplication/octet-stream; name=libpq_minmaxproto_v3.patch; x-unix-mode=0644Download
From f5abd8583e57004d587eab8d95a65defa6e6b133 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Sat, 30 Nov 2019 01:32:04 +0100
Subject: [PATCH] Allow setting min/max TLS protocol version in libpq
In the backend there are GUCs to control the minimum and maximum TLS
versions to allow for a connection, but the clientside libpq lacked
this ability. Disallowing servers which aren't providing secure TLS
protocols is of interest to clients, but we provide a maximum protocol
version setting by the same rationale as for the backend; to aid with
testing and to cope with misbehaving software.
This refactors the OpenSSL replacement functions for setting TLS
version from the backend to src/common to avoid code duplication.
The existing sha2_openssl.c file is renamed openssl_sha2.c for
easier grouping of OpenSSL common code.
---
doc/src/sgml/libpq.sgml | 65 ++++++++++
src/backend/libpq/be-secure-openssl.c | 99 +--------------
src/common/Makefile | 3 +-
src/common/openssl_protocol.c | 115 ++++++++++++++++++
src/common/{sha2_openssl.c => openssl_sha2.c} | 4 +-
src/include/common/openssl.h | 27 ++++
src/interfaces/libpq/fe-connect.c | 8 ++
src/interfaces/libpq/fe-secure-openssl.c | 81 ++++++++++++
src/interfaces/libpq/libpq-int.h | 2 +
src/test/ssl/t/001_ssltests.pl | 14 ++-
src/tools/msvc/Mkvcbuild.pm | 3 +-
11 files changed, 318 insertions(+), 103 deletions(-)
create mode 100644 src/common/openssl_protocol.c
rename src/common/{sha2_openssl.c => openssl_sha2.c} (97%)
create mode 100644 src/include/common/openssl.h
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 64cff49c4d..5c7816ce6f 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1727,6 +1727,33 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
</listitem>
</varlistentry>
+ <varlistentry id="libpq-connect-sslminprotocolversion" xreflabel="sslminprotocolversion">
+ <term><literal>sslminprotocolversion</literal></term>
+ <listitem>
+ <para>
+ This parameter specifies the minimum SSL/TLS protocol version to allow
+ for the connection. Valid values are <literal>TLSv1</literal>,
+ <literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
+ <literal>TLSv1.3</literal>. The supported protocols depend on the
+ version of <productname>OpenSSL</productname> used, older versions
+ doesn't support the modern protocol versions.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-connect-sslmaxprotocolversion" xreflabel="sslmaxprotocolversion">
+ <term><literal>sslmaxprotocolversion</literal></term>
+ <listitem>
+ <para>
+ This parameter specifies the maximum SSL/TLS protocol version to allow
+ for the connection. The supported values are the same as for <literal>
+ sslminprotocolversion</literal>. Setting a maximum protocol version is
+ generally only useful for testing, or in case there are software components
+ which doesn't support newer protocol versions.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="libpq-connect-krbsrvname" xreflabel="krbsrvname">
<term><literal>krbsrvname</literal></term>
<listitem>
@@ -7115,6 +7142,26 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
</para>
</listitem>
+ <listitem>
+ <para>
+ <indexterm>
+ <primary><envar>PGSSLMINPROTOCOLVERSION</envar></primary>
+ </indexterm>
+ <envar>PGSSLMINPROTOCOLVERSION</envar> behaves the same as the <xref
+ linkend="libpq-connect-sslminprotocolversion"/> connection parameter.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <indexterm>
+ <primary><envar>PGSSLMAXPROTOCOLVERSION</envar></primary>
+ </indexterm>
+ <envar>PGSSLMAXPROTOCOLVERSION</envar> behaves the same as the <xref
+ linkend="libpq-connect-sslminprotocolversion"/> connection parameter.
+ </para>
+ </listitem>
+
<listitem>
<para>
<indexterm>
@@ -7788,6 +7835,24 @@ ldap://ldap.acme.com/cn=dbserver,cn=hosts?pgconnectinfo?base?(objectclass=*)
</sect2>
+ <sect2>
+ <title>Client Protocol Usage</title>
+
+ <para>
+ When connecting using SSL, the client and server negotiate which protocol
+ to use for the connection. <productname>PostgreSQL</productname> supports
+ <literal>TLSv1</literal>, <literal>TLSv1.1</literal>, <literal>TLSv1.2</literal>
+ and <literal>TLSv1.3</literal>, but the protocols available depends on the
+ version of <productname>OpenSSL</productname> which the client is using.
+ The minimum requested version can be specified with <literal>sslminprotocolversion</literal>,
+ which will ensure that the connection use that version, or higher, or fails.
+ The maximum requested version can be specified with <literal>sslmaxprotocolversion</literal>,
+ but this is mainly only useful for testing, or in case a component doesn't
+ work with a newer protocol.
+ </para>
+
+ </sect2>
+
<sect2 id="libpq-ssl-fileusage">
<title>SSL Client File Usage</title>
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 62f1fcab2b..0cc59f1be1 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -36,6 +36,7 @@
#include <openssl/ec.h>
#endif
+#include "common/openssl.h"
#include "libpq/libpq.h"
#include "miscadmin.h"
#include "pgstat.h"
@@ -69,11 +70,6 @@ static bool ssl_is_server_start;
static int ssl_protocol_version_to_openssl(int v, const char *guc_name,
int loglevel);
-#ifndef SSL_CTX_set_min_proto_version
-static int SSL_CTX_set_min_proto_version(SSL_CTX *ctx, int version);
-static int SSL_CTX_set_max_proto_version(SSL_CTX *ctx, int version);
-#endif
-
/* ------------------------------------------------------------ */
/* Public interface */
@@ -1314,96 +1310,3 @@ ssl_protocol_version_to_openssl(int v, const char *guc_name, int loglevel)
GetConfigOption(guc_name, false, false))));
return -1;
}
-
-/*
- * Replacements for APIs present in newer versions of OpenSSL
- */
-#ifndef SSL_CTX_set_min_proto_version
-
-/*
- * OpenSSL versions that support TLS 1.3 shouldn't get here because they
- * already have these functions. So we don't have to keep updating the below
- * code for every new TLS version, and eventually it can go away. But let's
- * just check this to make sure ...
- */
-#ifdef TLS1_3_VERSION
-#error OpenSSL version mismatch
-#endif
-
-static int
-SSL_CTX_set_min_proto_version(SSL_CTX *ctx, int version)
-{
- int ssl_options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
-
- if (version > TLS1_VERSION)
- ssl_options |= SSL_OP_NO_TLSv1;
- /*
- * Some OpenSSL versions define TLS*_VERSION macros but not the
- * corresponding SSL_OP_NO_* macro, so in those cases we have to return
- * unsuccessfully here.
- */
-#ifdef TLS1_1_VERSION
- if (version > TLS1_1_VERSION)
- {
-#ifdef SSL_OP_NO_TLSv1_1
- ssl_options |= SSL_OP_NO_TLSv1_1;
-#else
- return 0;
-#endif
- }
-#endif
-#ifdef TLS1_2_VERSION
- if (version > TLS1_2_VERSION)
- {
-#ifdef SSL_OP_NO_TLSv1_2
- ssl_options |= SSL_OP_NO_TLSv1_2;
-#else
- return 0;
-#endif
- }
-#endif
-
- SSL_CTX_set_options(ctx, ssl_options);
-
- return 1; /* success */
-}
-
-static int
-SSL_CTX_set_max_proto_version(SSL_CTX *ctx, int version)
-{
- int ssl_options = 0;
-
- AssertArg(version != 0);
-
- /*
- * Some OpenSSL versions define TLS*_VERSION macros but not the
- * corresponding SSL_OP_NO_* macro, so in those cases we have to return
- * unsuccessfully here.
- */
-#ifdef TLS1_1_VERSION
- if (version < TLS1_1_VERSION)
- {
-#ifdef SSL_OP_NO_TLSv1_1
- ssl_options |= SSL_OP_NO_TLSv1_1;
-#else
- return 0;
-#endif
- }
-#endif
-#ifdef TLS1_2_VERSION
- if (version < TLS1_2_VERSION)
- {
-#ifdef SSL_OP_NO_TLSv1_2
- ssl_options |= SSL_OP_NO_TLSv1_2;
-#else
- return 0;
-#endif
- }
-#endif
-
- SSL_CTX_set_options(ctx, ssl_options);
-
- return 1; /* success */
-}
-
-#endif /* !SSL_CTX_set_min_proto_version */
diff --git a/src/common/Makefile b/src/common/Makefile
index ffb0f6edff..00c5001bbf 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -73,7 +73,8 @@ OBJS_COMMON = \
wait_error.o
ifeq ($(with_openssl),yes)
-OBJS_COMMON += sha2_openssl.o
+OBJS_COMMON += openssl_sha2.o \
+ openssl_protocol.o
else
OBJS_COMMON += sha2.o
endif
diff --git a/src/common/openssl_protocol.c b/src/common/openssl_protocol.c
new file mode 100644
index 0000000000..eef86b8a68
--- /dev/null
+++ b/src/common/openssl_protocol.c
@@ -0,0 +1,115 @@
+/*-------------------------------------------------------------------------
+ *
+ * openssl_protocol.c
+ * OpenSSL functionality shared between frontend and backend
+ *
+ * This should only be used if code is compiled with OpenSSL support.
+ *
+ * Portions Copyright (c) 2018-2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/openssl_protocol.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include <openssl/ssl.h>
+
+/*
+ * Replacements for APIs present in newer versions of OpenSSL
+ */
+#ifndef SSL_CTX_set_min_proto_version
+
+/*
+ * OpenSSL versions that support TLS 1.3 shouldn't get here because they
+ * already have these functions. So we don't have to keep updating the below
+ * code for every new TLS version, and eventually it can go away. But let's
+ * just check this to make sure ...
+ */
+#ifdef TLS1_3_VERSION
+#error OpenSSL version mismatch
+#endif
+
+static int
+SSL_CTX_set_min_proto_version(SSL_CTX *ctx, int version)
+{
+ int ssl_options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
+
+ if (version > TLS1_VERSION)
+ ssl_options |= SSL_OP_NO_TLSv1;
+ /*
+ * Some OpenSSL versions define TLS*_VERSION macros but not the
+ * corresponding SSL_OP_NO_* macro, so in those cases we have to return
+ * unsuccessfully here.
+ */
+#ifdef TLS1_1_VERSION
+ if (version > TLS1_1_VERSION)
+ {
+#ifdef SSL_OP_NO_TLSv1_1
+ ssl_options |= SSL_OP_NO_TLSv1_1;
+#else
+ return 0;
+#endif
+ }
+#endif
+#ifdef TLS1_2_VERSION
+ if (version > TLS1_2_VERSION)
+ {
+#ifdef SSL_OP_NO_TLSv1_2
+ ssl_options |= SSL_OP_NO_TLSv1_2;
+#else
+ return 0;
+#endif
+ }
+#endif
+
+ SSL_CTX_set_options(ctx, ssl_options);
+
+ return 1; /* success */
+}
+
+static int
+SSL_CTX_set_max_proto_version(SSL_CTX *ctx, int version)
+{
+ int ssl_options = 0;
+
+ AssertArg(version != 0);
+
+ /*
+ * Some OpenSSL versions define TLS*_VERSION macros but not the
+ * corresponding SSL_OP_NO_* macro, so in those cases we have to return
+ * unsuccessfully here.
+ */
+#ifdef TLS1_1_VERSION
+ if (version < TLS1_1_VERSION)
+ {
+#ifdef SSL_OP_NO_TLSv1_1
+ ssl_options |= SSL_OP_NO_TLSv1_1;
+#else
+ return 0;
+#endif
+ }
+#endif
+#ifdef TLS1_2_VERSION
+ if (version < TLS1_2_VERSION)
+ {
+#ifdef SSL_OP_NO_TLSv1_2
+ ssl_options |= SSL_OP_NO_TLSv1_2;
+#else
+ return 0;
+#endif
+ }
+#endif
+
+ SSL_CTX_set_options(ctx, ssl_options);
+
+ return 1; /* success */
+}
+
+#endif /* !SSL_CTX_set_min_proto_version */
diff --git a/src/common/sha2_openssl.c b/src/common/openssl_sha2.c
similarity index 97%
rename from src/common/sha2_openssl.c
rename to src/common/openssl_sha2.c
index 41673b3a88..7fa2196e34 100644
--- a/src/common/sha2_openssl.c
+++ b/src/common/openssl_sha2.c
@@ -1,6 +1,6 @@
/*-------------------------------------------------------------------------
*
- * sha2_openssl.c
+ * openssl_sha2.c
* Set of wrapper routines on top of OpenSSL to support SHA-224
* SHA-256, SHA-384 and SHA-512 functions.
*
@@ -9,7 +9,7 @@
* Portions Copyright (c) 2016-2020, PostgreSQL Global Development Group
*
* IDENTIFICATION
- * src/common/sha2_openssl.c
+ * src/common/openssl_sha2.c
*
*-------------------------------------------------------------------------
*/
diff --git a/src/include/common/openssl.h b/src/include/common/openssl.h
new file mode 100644
index 0000000000..b1aee24ee8
--- /dev/null
+++ b/src/include/common/openssl.h
@@ -0,0 +1,27 @@
+/*-------------------------------------------------------------------------
+ *
+ * openssl.h
+ * OpenSSL supporting functionality shared between frontend and backend
+ *
+ * Portions Copyright (c) 2018-2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/openssl.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _OPENSSL_H_
+#define _OPENSSL_H_
+
+#ifdef USE_OPENSSL
+#include <openssl/ssl.h>
+
+/* src/common/openssl_protocol.c */
+#ifndef SSL_CTX_set_min_proto_version
+static int SSL_CTX_set_min_proto_version(SSL_CTX *ctx, int version);
+static int SSL_CTX_set_max_proto_version(SSL_CTX *ctx, int version);
+#endif
+
+#endif
+
+#endif /* _OPENSSL_H_ */
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 80b54bc92b..a635639580 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -320,6 +320,14 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Require-Peer", "", 10,
offsetof(struct pg_conn, requirepeer)},
+ {"sslminprotocolversion", "PGSSLMINPROTOCOLVERSION", NULL, NULL,
+ "SSL-Minimum-Protocol-Version", "", /* sizeof("tlsv1.x") */ 7,
+ offsetof(struct pg_conn, sslminprotocolversion)},
+
+ {"sslmaxprotocolversion", "PGSSLMAXPROTOCOLVERSION", NULL, NULL,
+ "SSL-Maximum-Protocol-Version", "", /* sizeof("tlvs1.x") */ 7,
+ offsetof(struct pg_conn, sslmaxprotocolversion)},
+
/*
* As with SSL, all GSS options are exposed even in builds that don't have
* support.
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 0e84fc8ac6..f20d8fa287 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -30,6 +30,7 @@
#include "fe-auth.h"
#include "fe-secure-common.h"
#include "libpq-int.h"
+#include "common/openssl.h"
#ifdef WIN32
#include "win32.h"
@@ -95,6 +96,7 @@ static long win32_ssl_create_mutex = 0;
#endif /* ENABLE_THREAD_SAFETY */
static PQsslKeyPassHook_type PQsslKeyPassHook = NULL;
+static int ssl_protocol_version_to_openssl(const char *protocol);
/* ------------------------------------------------------------ */
/* Procedures common to all secure sessions */
@@ -787,6 +789,8 @@ initialize_SSL(PGconn *conn)
bool have_cert;
bool have_rootcert;
EVP_PKEY *pkey = NULL;
+ int ssl_max_ver;
+ int ssl_min_ver;
/*
* We'll need the home directory if any of the relevant parameters are
@@ -843,6 +847,52 @@ initialize_SSL(PGconn *conn)
/* Disable old protocol versions */
SSL_CTX_set_options(SSL_context, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
+ if (conn->sslminprotocolversion)
+ {
+ ssl_min_ver = ssl_protocol_version_to_openssl(conn->sslminprotocolversion);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid minimum protocol version specified: %s\n"),
+ conn->sslminprotocolversion);
+ return -1;
+ }
+
+ if (!SSL_CTX_set_min_proto_version(SSL_context, ssl_min_ver))
+ {
+ char *err = SSLerrmessage(ERR_get_error());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set minimum protocol version specified: %s\n"),
+ err);
+ return -1;
+ }
+ }
+
+ if (conn->sslmaxprotocolversion)
+ {
+ ssl_max_ver = ssl_protocol_version_to_openssl(conn->sslmaxprotocolversion);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid or unsupported maximum protocol version specified: %s\n"),
+ conn->sslmaxprotocolversion);
+ return -1;
+ }
+
+ if (!SSL_CTX_set_max_proto_version(SSL_context, ssl_max_ver))
+ {
+ char *err = SSLerrmessage(ERR_get_error());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set maximum SSL version specified: %s\n"),
+ err);
+ return -1;
+ }
+ }
+
/*
* Disable OpenSSL's moving-write-buffer sanity check, because it causes
* unnecessary failures in nonblocking send cases.
@@ -1659,3 +1709,34 @@ PQssl_passwd_cb(char *buf, int size, int rwflag, void *userdata)
else
return PQdefaultSSLKeyPassHook(buf, size, conn);
}
+
+/*
+ * Convert TLS protocol versionstring to OpenSSL values
+ *
+ * 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.
+ */
+static int
+ssl_protocol_version_to_openssl(const char *protocol)
+{
+ if ((pg_strcasecmp("tlsv1", protocol) == 0) || pg_strcasecmp("tlsv1.0", 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/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 79bc3780ff..72931e6019 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -367,6 +367,8 @@ struct pg_conn
char *krbsrvname; /* Kerberos service name */
char *gsslib; /* What GSS library to use ("gssapi" or
* "sspi") */
+ char *sslminprotocolversion; /* minimum TLS protocol version */
+ char *sslmaxprotocolversion; /* maximum TLS protocol version */
/* Type of connection to make. Possible values: any, read-write. */
char *target_session_attrs;
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 83fcd5e839..e7726bccfe 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -13,7 +13,7 @@ use SSLServer;
if ($ENV{with_openssl} eq 'yes')
{
- plan tests => 84;
+ plan tests => 87;
}
else
{
@@ -338,6 +338,18 @@ command_like(
^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,_null_,_null_,_null_\r?$}mx,
'pg_stat_ssl view without client certificate');
+# Test min/mix protocol versions
+test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require sslminprotocolversion=tlsv1.2 sslmaxprotocolversion=tlsv1.3",
+ "connect with correct range of allowed TLS protocol versions");
+
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require sslminprotocolversion=tlsv1.3 sslmaxprotocolversion=tlsv1.2",
+ qr/SSL error/,
+ "connect with an incorrect range of TLS protocol versions leaving no versions allowed");
+
### Server-side tests.
###
### Test certificate authorization.
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 3d6ef0de84..aa4593eb58 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -128,7 +128,8 @@ sub mkvcbuild
if ($solution->{options}->{openssl})
{
- push(@pgcommonallfiles, 'sha2_openssl.c');
+ push(@pgcommonallfiles, 'openssl_sha2.c');
+ push(@pgcommonallfiles, 'openssl_protocol.c');
}
else
{
--
2.21.0 (Apple Git-122.2)
Daniel Gustafsson <daniel@yesql.se> writes:
On 11 Jan 2020, at 03:49, Michael Paquier <michael@paquier.xyz> wrote:
One thing I noticed when looking at it is that we now have sha2_openssl.c and
openssl_protocol.c in src/common. For easier visual grouping of OpenSSL
functionality, it makes sense to me to rename sha2_openssl.c to openssl_sha2.c,
but that might just be pointless churn.
Databases like consistency, and so do I, so no issues from me to do a
rename of the sha2.c file. That makes sense with the addition of the
new file.
Done in the attached v3.
I'm kind of down on renaming files unless there is a *really* strong
reason for it. It makes back-patching more difficult and it makes
it much harder to follow the git history. And, seeing that there is
also a src/common/sha2.c, it seems to me that renaming sha2_openssl.c
will just break consistency in a different way.
Maybe the problem is you've got the new file's name backwards.
Maybe it should be protocol_openssl.c.
regards, tom lane
On 14 Jan 2020, at 15:49, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Daniel Gustafsson <daniel@yesql.se> writes:
On 11 Jan 2020, at 03:49, Michael Paquier <michael@paquier.xyz> wrote:
One thing I noticed when looking at it is that we now have sha2_openssl.c and
openssl_protocol.c in src/common. For easier visual grouping of OpenSSL
functionality, it makes sense to me to rename sha2_openssl.c to openssl_sha2.c,
but that might just be pointless churn.Databases like consistency, and so do I, so no issues from me to do a
rename of the sha2.c file. That makes sense with the addition of the
new file.Done in the attached v3.
I'm kind of down on renaming files unless there is a *really* strong
reason for it. It makes back-patching more difficult and it makes
it much harder to follow the git history. And, seeing that there is
also a src/common/sha2.c, it seems to me that renaming sha2_openssl.c
will just break consistency in a different way.Maybe the problem is you've got the new file's name backwards.
Maybe it should be protocol_openssl.c.
Thats a very good argument, I’ll send a v4 with protocol_openssl.c when back at the computer.
cheers ./daniel
On 14 Jan 2020, at 16:15, Daniel Gustafsson <daniel@yesql.se> wrote:
On 14 Jan 2020, at 15:49, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Daniel Gustafsson <daniel@yesql.se> writes:
On 11 Jan 2020, at 03:49, Michael Paquier <michael@paquier.xyz> wrote:
One thing I noticed when looking at it is that we now have sha2_openssl.c and
openssl_protocol.c in src/common. For easier visual grouping of OpenSSL
functionality, it makes sense to me to rename sha2_openssl.c to openssl_sha2.c,
but that might just be pointless churn.Databases like consistency, and so do I, so no issues from me to do a
rename of the sha2.c file. That makes sense with the addition of the
new file.Done in the attached v3.
I'm kind of down on renaming files unless there is a *really* strong
reason for it. It makes back-patching more difficult and it makes
it much harder to follow the git history. And, seeing that there is
also a src/common/sha2.c, it seems to me that renaming sha2_openssl.c
will just break consistency in a different way.Maybe the problem is you've got the new file's name backwards.
Maybe it should be protocol_openssl.c.Thats a very good argument, I’ll send a v4 with protocol_openssl.c when back at the computer.
Files renamed to match existing naming convention, the rest of the patch left
unchanged.
cheers ./daniel
Attachments:
libpq_minmaxproto_v4.patchapplication/octet-stream; name=libpq_minmaxproto_v4.patch; x-unix-mode=0644Download
From 759709c69b6d9e75eb0597d4355fb5854ceb7b4b Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Sat, 30 Nov 2019 01:32:04 +0100
Subject: [PATCH] Allow setting min/max TLS protocol version in libpq
In the backend there are GUCs to control the minimum and maximum TLS
versions to allow for a connection, but the clientside libpq lacked
this ability. Disallowing servers which aren't providing secure TLS
protocols is of interest to clients, but we provide a maximum protocol
version setting by the same rationale as for the backend; to aid with
testing and to cope with misbehaving software.
This refactors the OpenSSL replacement functions for setting TLS
version from the backend to src/common to avoid code duplication.
---
doc/src/sgml/libpq.sgml | 65 +++++++++++++
src/backend/libpq/be-secure-openssl.c | 99 +------------------
src/common/Makefile | 3 +-
src/common/protocol_openssl.c | 115 +++++++++++++++++++++++
src/include/common/openssl.h | 27 ++++++
src/interfaces/libpq/fe-connect.c | 8 ++
src/interfaces/libpq/fe-secure-openssl.c | 81 ++++++++++++++++
src/interfaces/libpq/libpq-int.h | 2 +
src/test/ssl/t/001_ssltests.pl | 14 ++-
src/tools/msvc/Mkvcbuild.pm | 1 +
10 files changed, 315 insertions(+), 100 deletions(-)
create mode 100644 src/common/protocol_openssl.c
create mode 100644 src/include/common/openssl.h
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 64cff49c4d..5c7816ce6f 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1727,6 +1727,33 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
</listitem>
</varlistentry>
+ <varlistentry id="libpq-connect-sslminprotocolversion" xreflabel="sslminprotocolversion">
+ <term><literal>sslminprotocolversion</literal></term>
+ <listitem>
+ <para>
+ This parameter specifies the minimum SSL/TLS protocol version to allow
+ for the connection. Valid values are <literal>TLSv1</literal>,
+ <literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
+ <literal>TLSv1.3</literal>. The supported protocols depend on the
+ version of <productname>OpenSSL</productname> used, older versions
+ doesn't support the modern protocol versions.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-connect-sslmaxprotocolversion" xreflabel="sslmaxprotocolversion">
+ <term><literal>sslmaxprotocolversion</literal></term>
+ <listitem>
+ <para>
+ This parameter specifies the maximum SSL/TLS protocol version to allow
+ for the connection. The supported values are the same as for <literal>
+ sslminprotocolversion</literal>. Setting a maximum protocol version is
+ generally only useful for testing, or in case there are software components
+ which doesn't support newer protocol versions.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="libpq-connect-krbsrvname" xreflabel="krbsrvname">
<term><literal>krbsrvname</literal></term>
<listitem>
@@ -7115,6 +7142,26 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
</para>
</listitem>
+ <listitem>
+ <para>
+ <indexterm>
+ <primary><envar>PGSSLMINPROTOCOLVERSION</envar></primary>
+ </indexterm>
+ <envar>PGSSLMINPROTOCOLVERSION</envar> behaves the same as the <xref
+ linkend="libpq-connect-sslminprotocolversion"/> connection parameter.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <indexterm>
+ <primary><envar>PGSSLMAXPROTOCOLVERSION</envar></primary>
+ </indexterm>
+ <envar>PGSSLMAXPROTOCOLVERSION</envar> behaves the same as the <xref
+ linkend="libpq-connect-sslminprotocolversion"/> connection parameter.
+ </para>
+ </listitem>
+
<listitem>
<para>
<indexterm>
@@ -7788,6 +7835,24 @@ ldap://ldap.acme.com/cn=dbserver,cn=hosts?pgconnectinfo?base?(objectclass=*)
</sect2>
+ <sect2>
+ <title>Client Protocol Usage</title>
+
+ <para>
+ When connecting using SSL, the client and server negotiate which protocol
+ to use for the connection. <productname>PostgreSQL</productname> supports
+ <literal>TLSv1</literal>, <literal>TLSv1.1</literal>, <literal>TLSv1.2</literal>
+ and <literal>TLSv1.3</literal>, but the protocols available depends on the
+ version of <productname>OpenSSL</productname> which the client is using.
+ The minimum requested version can be specified with <literal>sslminprotocolversion</literal>,
+ which will ensure that the connection use that version, or higher, or fails.
+ The maximum requested version can be specified with <literal>sslmaxprotocolversion</literal>,
+ but this is mainly only useful for testing, or in case a component doesn't
+ work with a newer protocol.
+ </para>
+
+ </sect2>
+
<sect2 id="libpq-ssl-fileusage">
<title>SSL Client File Usage</title>
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 62f1fcab2b..0cc59f1be1 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -36,6 +36,7 @@
#include <openssl/ec.h>
#endif
+#include "common/openssl.h"
#include "libpq/libpq.h"
#include "miscadmin.h"
#include "pgstat.h"
@@ -69,11 +70,6 @@ static bool ssl_is_server_start;
static int ssl_protocol_version_to_openssl(int v, const char *guc_name,
int loglevel);
-#ifndef SSL_CTX_set_min_proto_version
-static int SSL_CTX_set_min_proto_version(SSL_CTX *ctx, int version);
-static int SSL_CTX_set_max_proto_version(SSL_CTX *ctx, int version);
-#endif
-
/* ------------------------------------------------------------ */
/* Public interface */
@@ -1314,96 +1310,3 @@ ssl_protocol_version_to_openssl(int v, const char *guc_name, int loglevel)
GetConfigOption(guc_name, false, false))));
return -1;
}
-
-/*
- * Replacements for APIs present in newer versions of OpenSSL
- */
-#ifndef SSL_CTX_set_min_proto_version
-
-/*
- * OpenSSL versions that support TLS 1.3 shouldn't get here because they
- * already have these functions. So we don't have to keep updating the below
- * code for every new TLS version, and eventually it can go away. But let's
- * just check this to make sure ...
- */
-#ifdef TLS1_3_VERSION
-#error OpenSSL version mismatch
-#endif
-
-static int
-SSL_CTX_set_min_proto_version(SSL_CTX *ctx, int version)
-{
- int ssl_options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
-
- if (version > TLS1_VERSION)
- ssl_options |= SSL_OP_NO_TLSv1;
- /*
- * Some OpenSSL versions define TLS*_VERSION macros but not the
- * corresponding SSL_OP_NO_* macro, so in those cases we have to return
- * unsuccessfully here.
- */
-#ifdef TLS1_1_VERSION
- if (version > TLS1_1_VERSION)
- {
-#ifdef SSL_OP_NO_TLSv1_1
- ssl_options |= SSL_OP_NO_TLSv1_1;
-#else
- return 0;
-#endif
- }
-#endif
-#ifdef TLS1_2_VERSION
- if (version > TLS1_2_VERSION)
- {
-#ifdef SSL_OP_NO_TLSv1_2
- ssl_options |= SSL_OP_NO_TLSv1_2;
-#else
- return 0;
-#endif
- }
-#endif
-
- SSL_CTX_set_options(ctx, ssl_options);
-
- return 1; /* success */
-}
-
-static int
-SSL_CTX_set_max_proto_version(SSL_CTX *ctx, int version)
-{
- int ssl_options = 0;
-
- AssertArg(version != 0);
-
- /*
- * Some OpenSSL versions define TLS*_VERSION macros but not the
- * corresponding SSL_OP_NO_* macro, so in those cases we have to return
- * unsuccessfully here.
- */
-#ifdef TLS1_1_VERSION
- if (version < TLS1_1_VERSION)
- {
-#ifdef SSL_OP_NO_TLSv1_1
- ssl_options |= SSL_OP_NO_TLSv1_1;
-#else
- return 0;
-#endif
- }
-#endif
-#ifdef TLS1_2_VERSION
- if (version < TLS1_2_VERSION)
- {
-#ifdef SSL_OP_NO_TLSv1_2
- ssl_options |= SSL_OP_NO_TLSv1_2;
-#else
- return 0;
-#endif
- }
-#endif
-
- SSL_CTX_set_options(ctx, ssl_options);
-
- return 1; /* success */
-}
-
-#endif /* !SSL_CTX_set_min_proto_version */
diff --git a/src/common/Makefile b/src/common/Makefile
index ffb0f6edff..bec9c47aef 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -73,7 +73,8 @@ OBJS_COMMON = \
wait_error.o
ifeq ($(with_openssl),yes)
-OBJS_COMMON += sha2_openssl.o
+OBJS_COMMON += sha2_openssl.o \
+ protocol_openssl.o
else
OBJS_COMMON += sha2.o
endif
diff --git a/src/common/protocol_openssl.c b/src/common/protocol_openssl.c
new file mode 100644
index 0000000000..b55919a215
--- /dev/null
+++ b/src/common/protocol_openssl.c
@@ -0,0 +1,115 @@
+/*-------------------------------------------------------------------------
+ *
+ * protocol_openssl.c
+ * OpenSSL functionality shared between frontend and backend
+ *
+ * This should only be used if code is compiled with OpenSSL support.
+ *
+ * Portions Copyright (c) 2018-2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/protocol_openssl.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include <openssl/ssl.h>
+
+/*
+ * Replacements for APIs present in newer versions of OpenSSL
+ */
+#ifndef SSL_CTX_set_min_proto_version
+
+/*
+ * OpenSSL versions that support TLS 1.3 shouldn't get here because they
+ * already have these functions. So we don't have to keep updating the below
+ * code for every new TLS version, and eventually it can go away. But let's
+ * just check this to make sure ...
+ */
+#ifdef TLS1_3_VERSION
+#error OpenSSL version mismatch
+#endif
+
+static int
+SSL_CTX_set_min_proto_version(SSL_CTX *ctx, int version)
+{
+ int ssl_options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
+
+ if (version > TLS1_VERSION)
+ ssl_options |= SSL_OP_NO_TLSv1;
+ /*
+ * Some OpenSSL versions define TLS*_VERSION macros but not the
+ * corresponding SSL_OP_NO_* macro, so in those cases we have to return
+ * unsuccessfully here.
+ */
+#ifdef TLS1_1_VERSION
+ if (version > TLS1_1_VERSION)
+ {
+#ifdef SSL_OP_NO_TLSv1_1
+ ssl_options |= SSL_OP_NO_TLSv1_1;
+#else
+ return 0;
+#endif
+ }
+#endif
+#ifdef TLS1_2_VERSION
+ if (version > TLS1_2_VERSION)
+ {
+#ifdef SSL_OP_NO_TLSv1_2
+ ssl_options |= SSL_OP_NO_TLSv1_2;
+#else
+ return 0;
+#endif
+ }
+#endif
+
+ SSL_CTX_set_options(ctx, ssl_options);
+
+ return 1; /* success */
+}
+
+static int
+SSL_CTX_set_max_proto_version(SSL_CTX *ctx, int version)
+{
+ int ssl_options = 0;
+
+ AssertArg(version != 0);
+
+ /*
+ * Some OpenSSL versions define TLS*_VERSION macros but not the
+ * corresponding SSL_OP_NO_* macro, so in those cases we have to return
+ * unsuccessfully here.
+ */
+#ifdef TLS1_1_VERSION
+ if (version < TLS1_1_VERSION)
+ {
+#ifdef SSL_OP_NO_TLSv1_1
+ ssl_options |= SSL_OP_NO_TLSv1_1;
+#else
+ return 0;
+#endif
+ }
+#endif
+#ifdef TLS1_2_VERSION
+ if (version < TLS1_2_VERSION)
+ {
+#ifdef SSL_OP_NO_TLSv1_2
+ ssl_options |= SSL_OP_NO_TLSv1_2;
+#else
+ return 0;
+#endif
+ }
+#endif
+
+ SSL_CTX_set_options(ctx, ssl_options);
+
+ return 1; /* success */
+}
+
+#endif /* !SSL_CTX_set_min_proto_version */
diff --git a/src/include/common/openssl.h b/src/include/common/openssl.h
new file mode 100644
index 0000000000..bf37f8d56c
--- /dev/null
+++ b/src/include/common/openssl.h
@@ -0,0 +1,27 @@
+/*-------------------------------------------------------------------------
+ *
+ * openssl.h
+ * OpenSSL supporting functionality shared between frontend and backend
+ *
+ * Portions Copyright (c) 2018-2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/openssl.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef COMMON_OPENSSL_H
+#define COMMON_OPENSSL_H
+
+#ifdef USE_OPENSSL
+#include <openssl/ssl.h>
+
+/* src/common/protocol_openssl.c */
+#ifndef SSL_CTX_set_min_proto_version
+static int SSL_CTX_set_min_proto_version(SSL_CTX *ctx, int version);
+static int SSL_CTX_set_max_proto_version(SSL_CTX *ctx, int version);
+#endif
+
+#endif
+
+#endif /* COMMON_OPENSSL_H */
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 80b54bc92b..a635639580 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -320,6 +320,14 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Require-Peer", "", 10,
offsetof(struct pg_conn, requirepeer)},
+ {"sslminprotocolversion", "PGSSLMINPROTOCOLVERSION", NULL, NULL,
+ "SSL-Minimum-Protocol-Version", "", /* sizeof("tlsv1.x") */ 7,
+ offsetof(struct pg_conn, sslminprotocolversion)},
+
+ {"sslmaxprotocolversion", "PGSSLMAXPROTOCOLVERSION", NULL, NULL,
+ "SSL-Maximum-Protocol-Version", "", /* sizeof("tlvs1.x") */ 7,
+ offsetof(struct pg_conn, sslmaxprotocolversion)},
+
/*
* As with SSL, all GSS options are exposed even in builds that don't have
* support.
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 0e84fc8ac6..f20d8fa287 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -30,6 +30,7 @@
#include "fe-auth.h"
#include "fe-secure-common.h"
#include "libpq-int.h"
+#include "common/openssl.h"
#ifdef WIN32
#include "win32.h"
@@ -95,6 +96,7 @@ static long win32_ssl_create_mutex = 0;
#endif /* ENABLE_THREAD_SAFETY */
static PQsslKeyPassHook_type PQsslKeyPassHook = NULL;
+static int ssl_protocol_version_to_openssl(const char *protocol);
/* ------------------------------------------------------------ */
/* Procedures common to all secure sessions */
@@ -787,6 +789,8 @@ initialize_SSL(PGconn *conn)
bool have_cert;
bool have_rootcert;
EVP_PKEY *pkey = NULL;
+ int ssl_max_ver;
+ int ssl_min_ver;
/*
* We'll need the home directory if any of the relevant parameters are
@@ -843,6 +847,52 @@ initialize_SSL(PGconn *conn)
/* Disable old protocol versions */
SSL_CTX_set_options(SSL_context, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
+ if (conn->sslminprotocolversion)
+ {
+ ssl_min_ver = ssl_protocol_version_to_openssl(conn->sslminprotocolversion);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid minimum protocol version specified: %s\n"),
+ conn->sslminprotocolversion);
+ return -1;
+ }
+
+ if (!SSL_CTX_set_min_proto_version(SSL_context, ssl_min_ver))
+ {
+ char *err = SSLerrmessage(ERR_get_error());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set minimum protocol version specified: %s\n"),
+ err);
+ return -1;
+ }
+ }
+
+ if (conn->sslmaxprotocolversion)
+ {
+ ssl_max_ver = ssl_protocol_version_to_openssl(conn->sslmaxprotocolversion);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid or unsupported maximum protocol version specified: %s\n"),
+ conn->sslmaxprotocolversion);
+ return -1;
+ }
+
+ if (!SSL_CTX_set_max_proto_version(SSL_context, ssl_max_ver))
+ {
+ char *err = SSLerrmessage(ERR_get_error());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set maximum SSL version specified: %s\n"),
+ err);
+ return -1;
+ }
+ }
+
/*
* Disable OpenSSL's moving-write-buffer sanity check, because it causes
* unnecessary failures in nonblocking send cases.
@@ -1659,3 +1709,34 @@ PQssl_passwd_cb(char *buf, int size, int rwflag, void *userdata)
else
return PQdefaultSSLKeyPassHook(buf, size, conn);
}
+
+/*
+ * Convert TLS protocol versionstring to OpenSSL values
+ *
+ * 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.
+ */
+static int
+ssl_protocol_version_to_openssl(const char *protocol)
+{
+ if ((pg_strcasecmp("tlsv1", protocol) == 0) || pg_strcasecmp("tlsv1.0", 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/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 79bc3780ff..72931e6019 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -367,6 +367,8 @@ struct pg_conn
char *krbsrvname; /* Kerberos service name */
char *gsslib; /* What GSS library to use ("gssapi" or
* "sspi") */
+ char *sslminprotocolversion; /* minimum TLS protocol version */
+ char *sslmaxprotocolversion; /* maximum TLS protocol version */
/* Type of connection to make. Possible values: any, read-write. */
char *target_session_attrs;
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 83fcd5e839..e7726bccfe 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -13,7 +13,7 @@ use SSLServer;
if ($ENV{with_openssl} eq 'yes')
{
- plan tests => 84;
+ plan tests => 87;
}
else
{
@@ -338,6 +338,18 @@ command_like(
^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,_null_,_null_,_null_\r?$}mx,
'pg_stat_ssl view without client certificate');
+# Test min/mix protocol versions
+test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require sslminprotocolversion=tlsv1.2 sslmaxprotocolversion=tlsv1.3",
+ "connect with correct range of allowed TLS protocol versions");
+
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require sslminprotocolversion=tlsv1.3 sslmaxprotocolversion=tlsv1.2",
+ qr/SSL error/,
+ "connect with an incorrect range of TLS protocol versions leaving no versions allowed");
+
### Server-side tests.
###
### Test certificate authorization.
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 3d6ef0de84..d3bc6c92d5 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -129,6 +129,7 @@ sub mkvcbuild
if ($solution->{options}->{openssl})
{
push(@pgcommonallfiles, 'sha2_openssl.c');
+ push(@pgcommonallfiles, 'protocol_openssl.c');
}
else
{
--
2.21.0 (Apple Git-122.2)
On Tue, Jan 14, 2020 at 11:01:00PM +0100, Daniel Gustafsson wrote:
Files renamed to match existing naming convention, the rest of the patch left
unchanged.
+ if ((pg_strcasecmp("tlsv1", protocol) == 0) || pg_strcasecmp("tlsv1.0", protocol) == 0)
+ return TLS1_VERSION;
"TLSv1.0" is not a supported grammar in the backend. So I would just
drop it in libpq. It is also not documented.
+ * Portions Copyright (c) 2018-2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/protocol_openssl.c
It is a nobrainer to just use those lines for copyright notices
instead:
* Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
+ <varlistentry id="libpq-connect-sslminprotocolversion"
xreflabel="sslminprotocolversion">
+ <term><literal>sslminprotocolversion</literal></term>
Got to wonder if we had better not use underscores for those new
parameter names as they are much longer than their cousins.
Underscores would make things more inconsistent.
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid minimum protocol version specified: %s\n"),
+ conn->sslminprotocolversion);
+ return -1;
+ }
[...]
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid or unsupported maximum protocol version specified: %s\n"),
+ conn->sslmaxprotocolversion);
+ return -1;
+ }
Error messages for the min/max are inconsistent. I would just use
"unsupported", because...
Following with your complain on the other thread about the GUC
handling for the min/max protocol parameter. Shouldn't you validate
the supported values in connectOptions2() like what's done for the
other parameters? This way, you can make the difference between an
invalid value and an unsupported value with two different error
strings. By validating the values at an earlier stage, you save a
couple of cycles for the application.
+ <literal>TLSv1.3</literal>. The supported protocols depend on the
+ version of <productname>OpenSSL</productname> used, older versions
+ doesn't support the modern protocol versions.
Incorrect grammar => s/doesn't/don't/.
It would be good to mention that the default is no value, meaning that
the minimum and/or the maximum are not enforced in the SSL context.
+ if (conn->sslminprotocolversion)
+ {
[...]
+ if (conn->sslmaxprotocolversion)
+ {
You are missing two checks for empty strings here (aka strlen == 0).
These should be treated the same as no value to enforce the protocol
to. (Let's not add an alias for "any".)
+ * Convert TLS protocol versionstring to OpenSSL values
Space needed here => "version string".
A nit, perhaps unnecessary, but I would use "TLSv1.1", etc. in the
values harcoded for libpq. That's easier to grep after, and
consistent with the existing conventions even if you apply a
case-insensitive comparison.
--
Michael
On Wed, Jan 15, 2020 at 02:58:09PM +0900, Michael Paquier wrote:
On Tue, Jan 14, 2020 at 11:01:00PM +0100, Daniel Gustafsson wrote:
Files renamed to match existing naming convention, the rest of the patch left
unchanged.[previous review]
One thing I remembered after sleeping on it is that we can split the
patch into two parts: the refactoring pieces and the addition of the
options for libpq. The previous review mostly impacts the libpq part,
and the split is straight-forward, so attached is a patch for only the
refactoring pieces with some fixes and tweaks. I have tested it with
and without OpenSSL, using 1.0.2 and 1.1.0 on Linux and Windows
(MSVC). Those tests have allowed me to find an error in the previous
patch that I missed: the new files openssl.h and protocol_openssl.c
still declared SSL_CTX_set_min/max_proto_version as static functions,
so compilation was broken when trying to use OpenSSL <= 1.0.2.
If that looks fine, I would like to get that part committed first.
Daniel, any thoughts?
--
Michael
Attachments:
openssl-proto-refactor-v1.patchtext/x-diff; charset=us-asciiDownload
diff --git a/src/include/common/openssl.h b/src/include/common/openssl.h
new file mode 100644
index 0000000000..47fa129994
--- /dev/null
+++ b/src/include/common/openssl.h
@@ -0,0 +1,28 @@
+/*-------------------------------------------------------------------------
+ *
+ * openssl.h
+ * OpenSSL supporting functionality shared between frontend and backend
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/include/common/openssl.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef COMMON_OPENSSL_H
+#define COMMON_OPENSSL_H
+
+#ifdef USE_OPENSSL
+#include <openssl/ssl.h>
+
+/* src/common/protocol_openssl.c */
+#ifndef SSL_CTX_set_min_proto_version
+extern int SSL_CTX_set_min_proto_version(SSL_CTX *ctx, int version);
+extern int SSL_CTX_set_max_proto_version(SSL_CTX *ctx, int version);
+#endif
+
+#endif
+
+#endif /* COMMON_OPENSSL_H */
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 62f1fcab2b..0cc59f1be1 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -36,6 +36,7 @@
#include <openssl/ec.h>
#endif
+#include "common/openssl.h"
#include "libpq/libpq.h"
#include "miscadmin.h"
#include "pgstat.h"
@@ -69,11 +70,6 @@ static bool ssl_is_server_start;
static int ssl_protocol_version_to_openssl(int v, const char *guc_name,
int loglevel);
-#ifndef SSL_CTX_set_min_proto_version
-static int SSL_CTX_set_min_proto_version(SSL_CTX *ctx, int version);
-static int SSL_CTX_set_max_proto_version(SSL_CTX *ctx, int version);
-#endif
-
/* ------------------------------------------------------------ */
/* Public interface */
@@ -1314,96 +1310,3 @@ ssl_protocol_version_to_openssl(int v, const char *guc_name, int loglevel)
GetConfigOption(guc_name, false, false))));
return -1;
}
-
-/*
- * Replacements for APIs present in newer versions of OpenSSL
- */
-#ifndef SSL_CTX_set_min_proto_version
-
-/*
- * OpenSSL versions that support TLS 1.3 shouldn't get here because they
- * already have these functions. So we don't have to keep updating the below
- * code for every new TLS version, and eventually it can go away. But let's
- * just check this to make sure ...
- */
-#ifdef TLS1_3_VERSION
-#error OpenSSL version mismatch
-#endif
-
-static int
-SSL_CTX_set_min_proto_version(SSL_CTX *ctx, int version)
-{
- int ssl_options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
-
- if (version > TLS1_VERSION)
- ssl_options |= SSL_OP_NO_TLSv1;
- /*
- * Some OpenSSL versions define TLS*_VERSION macros but not the
- * corresponding SSL_OP_NO_* macro, so in those cases we have to return
- * unsuccessfully here.
- */
-#ifdef TLS1_1_VERSION
- if (version > TLS1_1_VERSION)
- {
-#ifdef SSL_OP_NO_TLSv1_1
- ssl_options |= SSL_OP_NO_TLSv1_1;
-#else
- return 0;
-#endif
- }
-#endif
-#ifdef TLS1_2_VERSION
- if (version > TLS1_2_VERSION)
- {
-#ifdef SSL_OP_NO_TLSv1_2
- ssl_options |= SSL_OP_NO_TLSv1_2;
-#else
- return 0;
-#endif
- }
-#endif
-
- SSL_CTX_set_options(ctx, ssl_options);
-
- return 1; /* success */
-}
-
-static int
-SSL_CTX_set_max_proto_version(SSL_CTX *ctx, int version)
-{
- int ssl_options = 0;
-
- AssertArg(version != 0);
-
- /*
- * Some OpenSSL versions define TLS*_VERSION macros but not the
- * corresponding SSL_OP_NO_* macro, so in those cases we have to return
- * unsuccessfully here.
- */
-#ifdef TLS1_1_VERSION
- if (version < TLS1_1_VERSION)
- {
-#ifdef SSL_OP_NO_TLSv1_1
- ssl_options |= SSL_OP_NO_TLSv1_1;
-#else
- return 0;
-#endif
- }
-#endif
-#ifdef TLS1_2_VERSION
- if (version < TLS1_2_VERSION)
- {
-#ifdef SSL_OP_NO_TLSv1_2
- ssl_options |= SSL_OP_NO_TLSv1_2;
-#else
- return 0;
-#endif
- }
-#endif
-
- SSL_CTX_set_options(ctx, ssl_options);
-
- return 1; /* success */
-}
-
-#endif /* !SSL_CTX_set_min_proto_version */
diff --git a/src/common/Makefile b/src/common/Makefile
index ffb0f6edff..55161f05a2 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -73,7 +73,9 @@ OBJS_COMMON = \
wait_error.o
ifeq ($(with_openssl),yes)
-OBJS_COMMON += sha2_openssl.o
+OBJS_COMMON += \
+ protocol_openssl.o \
+ sha2_openssl.o
else
OBJS_COMMON += sha2.o
endif
diff --git a/src/common/protocol_openssl.c b/src/common/protocol_openssl.c
new file mode 100644
index 0000000000..253b611ea2
--- /dev/null
+++ b/src/common/protocol_openssl.c
@@ -0,0 +1,117 @@
+/*-------------------------------------------------------------------------
+ *
+ * protocol_openssl.c
+ * OpenSSL functionality shared between frontend and backend
+ *
+ * This should only be used if code is compiled with OpenSSL support.
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/protocol_openssl.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include <openssl/ssl.h>
+
+/*
+ * Replacements for APIs introduced in OpenSSL 1.1.0.
+ */
+#ifndef SSL_CTX_set_min_proto_version
+
+/*
+ * OpenSSL versions that support TLS 1.3 shouldn't get here because they
+ * already have these functions. So we don't have to keep updating the below
+ * code for every new TLS version, and eventually it can go away. But let's
+ * just check this to make sure ...
+ */
+#ifdef TLS1_3_VERSION
+#error OpenSSL version mismatch
+#endif
+
+int
+SSL_CTX_set_min_proto_version(SSL_CTX *ctx, int version)
+{
+ int ssl_options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
+
+ if (version > TLS1_VERSION)
+ ssl_options |= SSL_OP_NO_TLSv1;
+
+ /*
+ * Some OpenSSL versions define TLS*_VERSION macros but not the
+ * corresponding SSL_OP_NO_* macro, so in those cases we have to return
+ * unsuccessfully here.
+ */
+#ifdef TLS1_1_VERSION
+ if (version > TLS1_1_VERSION)
+ {
+#ifdef SSL_OP_NO_TLSv1_1
+ ssl_options |= SSL_OP_NO_TLSv1_1;
+#else
+ return 0;
+#endif
+ }
+#endif
+#ifdef TLS1_2_VERSION
+ if (version > TLS1_2_VERSION)
+ {
+#ifdef SSL_OP_NO_TLSv1_2
+ ssl_options |= SSL_OP_NO_TLSv1_2;
+#else
+ return 0;
+#endif
+ }
+#endif
+
+ SSL_CTX_set_options(ctx, ssl_options);
+
+ return 1; /* success */
+}
+
+int
+SSL_CTX_set_max_proto_version(SSL_CTX *ctx, int version)
+{
+ int ssl_options = 0;
+
+ AssertArg(version != 0);
+
+ /*
+ * Some OpenSSL versions define TLS*_VERSION macros but not the
+ * corresponding SSL_OP_NO_* macro, so in those cases we have to return
+ * unsuccessfully here.
+ */
+#ifdef TLS1_1_VERSION
+ if (version < TLS1_1_VERSION)
+ {
+#ifdef SSL_OP_NO_TLSv1_1
+ ssl_options |= SSL_OP_NO_TLSv1_1;
+#else
+ return 0;
+#endif
+ }
+#endif
+#ifdef TLS1_2_VERSION
+ if (version < TLS1_2_VERSION)
+ {
+#ifdef SSL_OP_NO_TLSv1_2
+ ssl_options |= SSL_OP_NO_TLSv1_2;
+#else
+ return 0;
+#endif
+ }
+#endif
+
+ SSL_CTX_set_options(ctx, ssl_options);
+
+ return 1; /* success */
+}
+
+#endif /* !SSL_CTX_set_min_proto_version */
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index f6ab0d528b..e4f27a692a 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -129,6 +129,7 @@ sub mkvcbuild
if ($solution->{options}->{openssl})
{
push(@pgcommonallfiles, 'sha2_openssl.c');
+ push(@pgcommonallfiles, 'protocol_openssl.c');
}
else
{
On 16 Jan 2020, at 04:22, Michael Paquier <michael@paquier.xyz> wrote:
On Wed, Jan 15, 2020 at 02:58:09PM +0900, Michael Paquier wrote:
On Tue, Jan 14, 2020 at 11:01:00PM +0100, Daniel Gustafsson wrote:
Files renamed to match existing naming convention, the rest of the patch left
unchanged.[previous review]
One thing I remembered after sleeping on it is that we can split the
patch into two parts: the refactoring pieces and the addition of the
options for libpq.
Correct, they are mostly independent (the refactoring doesn't make a lot of
sense without the follow-up patch, but the min/max patch can be kept more
readable without the refactoring in it as well).
The previous review mostly impacts the libpq part,
and the split is straight-forward, so attached is a patch for only the
refactoring pieces with some fixes and tweaks. I have tested it with
and without OpenSSL, using 1.0.2 and 1.1.0 on Linux and Windows
(MSVC). Those tests have allowed me to find an error in the previous
patch that I missed: the new files openssl.h and protocol_openssl.c
still declared SSL_CTX_set_min/max_proto_version as static functions,
so compilation was broken when trying to use OpenSSL <= 1.0.2.
Doh .. thanks.
If that looks fine, I would like to get that part committed first.
Daniel, any thoughts?
The patch looks fine to me, I don't an issue with splitting it into a
refactoring patch and a TLS min/max version patch.
cheers ./daniel
On Thu, Jan 16, 2020 at 09:56:01AM +0100, Daniel Gustafsson wrote:
The patch looks fine to me, I don't an issue with splitting it into a
refactoring patch and a TLS min/max version patch.
Thanks, committed the refactoring part then. If the buildfarm breaks
for a reason or another, the window to look at is narrower than if we
had the full set of changes, and the git history is cleaner. I
noticed as well a compilation warning when compiling with OpenSSL
1.0.2 from protocol_openssl.c because of missing declarations of the
two routines because the header declaration was incorrect.
Could you please rebase and fix the remaining pieces of the patch?
--
Michael
On Fri, Jan 17, 2020 at 10:09:54AM +0900, Michael Paquier wrote:
Could you please rebase and fix the remaining pieces of the patch?
And while I remember, you may want to add checks for incorrect bounds
when validating the values in fe-connect.c... The same arguments as
for the backend part apply because we'd want to make the
implementation a maximum pluggable with all SSL libraries.
--
Michael
On 17 Jan 2020, at 03:38, Michael Paquier <michael@paquier.xyz> wrote:
On Fri, Jan 17, 2020 at 10:09:54AM +0900, Michael Paquier wrote:
Could you please rebase and fix the remaining pieces of the patch?
And while I remember, you may want to add checks for incorrect bounds
when validating the values in fe-connect.c... The same arguments as
for the backend part apply because we'd want to make the
implementation a maximum pluggable with all SSL libraries.
Agreed.
Attached is a v5 of the patch which hopefully address all the comments raised,
sorry for the delay.
cheers ./daniel
Attachments:
libpq_minmaxproto_v5.patchapplication/octet-stream; name=libpq_minmaxproto_v5.patch; x-unix-mode=0644Download
From ce450afe422080264c6b395f183f1330c828f404 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Fri, 24 Jan 2020 12:12:55 +0100
Subject: [PATCH] Allow setting min/max TLS protocol version in libpq
In the backend there are GUCs to control the minimum and maximum TLS
versions to allow for a connection, but the clientside libpq lacked
this ability. Disallowing servers which aren't providing secure TLS
protocols is of interest to clients, but we provide a maximum protocol
version setting by the same rationale as for the backend; to aid with
testing and to cope with misbehaving software.
---
doc/src/sgml/libpq.sgml | 67 ++++++++++++++++++++
src/interfaces/libpq/fe-connect.c | 44 +++++++++++++
src/interfaces/libpq/fe-secure-openssl.c | 81 ++++++++++++++++++++++++
src/interfaces/libpq/fe-secure.c | 50 +++++++++++++++
src/interfaces/libpq/libpq-int.h | 4 ++
src/test/ssl/t/001_ssltests.pl | 26 +++++++-
6 files changed, 271 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index fcbf7fafbd..a4f0c305ae 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1732,6 +1732,35 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
</listitem>
</varlistentry>
+ <varlistentry id="libpq-connect-sslminprotocolversion" xreflabel="sslminprotocolversion">
+ <term><literal>sslminprotocolversion</literal></term>
+ <listitem>
+ <para>
+ This parameter specifies the minimum SSL/TLS protocol version to allow
+ for the connection. Valid values are <literal>TLSv1</literal>,
+ <literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
+ <literal>TLSv1.3</literal>. The supported protocols depend on the
+ version of <productname>OpenSSL</productname> used, older versions
+ don't support the modern protocol versions. If not set, the system
+ wide default configuration is used.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-connect-sslmaxprotocolversion" xreflabel="sslmaxprotocolversion">
+ <term><literal>sslmaxprotocolversion</literal></term>
+ <listitem>
+ <para>
+ This parameter specifies the maximum SSL/TLS protocol version to allow
+ for the connection. The supported values are the same as for <literal>
+ sslminprotocolversion</literal>. Setting a maximum protocol version is
+ generally only useful for testing, or in case there are software components
+ which don't support newer protocol versions. If not set, the system
+ wide default configuration is used.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="libpq-connect-krbsrvname" xreflabel="krbsrvname">
<term><literal>krbsrvname</literal></term>
<listitem>
@@ -7120,6 +7149,26 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
</para>
</listitem>
+ <listitem>
+ <para>
+ <indexterm>
+ <primary><envar>PGSSLMINPROTOCOLVERSION</envar></primary>
+ </indexterm>
+ <envar>PGSSLMINPROTOCOLVERSION</envar> behaves the same as the <xref
+ linkend="libpq-connect-sslminprotocolversion"/> connection parameter.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <indexterm>
+ <primary><envar>PGSSLMAXPROTOCOLVERSION</envar></primary>
+ </indexterm>
+ <envar>PGSSLMAXPROTOCOLVERSION</envar> behaves the same as the <xref
+ linkend="libpq-connect-sslminprotocolversion"/> connection parameter.
+ </para>
+ </listitem>
+
<listitem>
<para>
<indexterm>
@@ -7793,6 +7842,24 @@ ldap://ldap.acme.com/cn=dbserver,cn=hosts?pgconnectinfo?base?(objectclass=*)
</sect2>
+ <sect2>
+ <title>Client Protocol Usage</title>
+
+ <para>
+ When connecting using SSL, the client and server negotiate which protocol
+ to use for the connection. <productname>PostgreSQL</productname> supports
+ <literal>TLSv1</literal>, <literal>TLSv1.1</literal>, <literal>TLSv1.2</literal>
+ and <literal>TLSv1.3</literal>, but the protocols available depends on the
+ version of <productname>OpenSSL</productname> which the client is using.
+ The minimum requested version can be specified with <literal>sslminprotocolversion</literal>,
+ which will ensure that the connection use that version, or higher, or fails.
+ The maximum requested version can be specified with <literal>sslmaxprotocolversion</literal>,
+ but this is mainly only useful for testing, or in case a component doesn't
+ work with a newer protocol.
+ </para>
+
+ </sect2>
+
<sect2 id="libpq-ssl-fileusage">
<title>SSL Client File Usage</title>
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 80b54bc92b..54a5609b9a 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -320,6 +320,14 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Require-Peer", "", 10,
offsetof(struct pg_conn, requirepeer)},
+ {"sslminprotocolversion", "PGSSLMINPROTOCOLVERSION", NULL, NULL,
+ "SSL-Minimum-Protocol-Version", "", /* sizeof("TLSv1.x") */ 7,
+ offsetof(struct pg_conn, sslminprotocolversion)},
+
+ {"sslmaxprotocolversion", "PGSSLMAXPROTOCOLVERSION", NULL, NULL,
+ "SSL-Maximum-Protocol-Version", "", /* sizeof("TLSv1.x") */ 7,
+ offsetof(struct pg_conn, sslmaxprotocolversion)},
+
/*
* As with SSL, all GSS options are exposed even in builds that don't have
* support.
@@ -1285,6 +1293,38 @@ connectOptions2(PGconn *conn)
goto oom_error;
}
+ /*
+ * Validate TLS protocol options sslminprotocolversion and
+ * sslmaxprotocolversion.
+ */
+ if (conn->sslminprotocolversion
+ && !pq_verify_ssl_protocol_option(conn->sslminprotocolversion))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid sslminprotocolversion value: \"%s\"\n"),
+ conn->sslminprotocolversion);
+ return false;
+ }
+ if (conn->sslmaxprotocolversion
+ && !pq_verify_ssl_protocol_option(conn->sslmaxprotocolversion))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid sslmaxprotocolversion value: \"%s\"\n"),
+ conn->sslmaxprotocolversion);
+ return false;
+ }
+
+ if (conn->sslminprotocolversion && conn->sslmaxprotocolversion)
+ {
+ if (!pq_verify_ssl_protocol_range(conn->sslminprotocolversion,
+ conn->sslmaxprotocolversion))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid protocol version range"));
+ return false;
+ }
+ }
+
/*
* validate gssencmode option
*/
@@ -4001,6 +4041,10 @@ freePGconn(PGconn *conn)
free(conn->sslcompression);
if (conn->requirepeer)
free(conn->requirepeer);
+ if (conn->sslminprotocolversion)
+ free(conn->sslminprotocolversion);
+ if (conn->sslmaxprotocolversion)
+ free(conn->sslmaxprotocolversion);
if (conn->gssencmode)
free(conn->gssencmode);
if (conn->krbsrvname)
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 0e84fc8ac6..8ee6572ce1 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -30,6 +30,7 @@
#include "fe-auth.h"
#include "fe-secure-common.h"
#include "libpq-int.h"
+#include "common/openssl.h"
#ifdef WIN32
#include "win32.h"
@@ -95,6 +96,7 @@ static long win32_ssl_create_mutex = 0;
#endif /* ENABLE_THREAD_SAFETY */
static PQsslKeyPassHook_type PQsslKeyPassHook = NULL;
+static int ssl_protocol_version_to_openssl(const char *protocol);
/* ------------------------------------------------------------ */
/* Procedures common to all secure sessions */
@@ -787,6 +789,8 @@ initialize_SSL(PGconn *conn)
bool have_cert;
bool have_rootcert;
EVP_PKEY *pkey = NULL;
+ int ssl_max_ver;
+ int ssl_min_ver;
/*
* We'll need the home directory if any of the relevant parameters are
@@ -843,6 +847,52 @@ initialize_SSL(PGconn *conn)
/* Disable old protocol versions */
SSL_CTX_set_options(SSL_context, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
+ if (conn->sslminprotocolversion && strlen(conn->sslminprotocolversion) > 0)
+ {
+ ssl_min_ver = ssl_protocol_version_to_openssl(conn->sslminprotocolversion);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid minimum protocol version specified: %s\n"),
+ conn->sslminprotocolversion);
+ return -1;
+ }
+
+ if (!SSL_CTX_set_min_proto_version(SSL_context, ssl_min_ver))
+ {
+ char *err = SSLerrmessage(ERR_get_error());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set minimum protocol version specified: %s\n"),
+ err);
+ return -1;
+ }
+ }
+
+ if (conn->sslmaxprotocolversion && strlen(conn->sslmaxprotocolversion) > 0)
+ {
+ ssl_max_ver = ssl_protocol_version_to_openssl(conn->sslmaxprotocolversion);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid or unsupported maximum protocol version specified: %s\n"),
+ conn->sslmaxprotocolversion);
+ return -1;
+ }
+
+ if (!SSL_CTX_set_max_proto_version(SSL_context, ssl_max_ver))
+ {
+ char *err = SSLerrmessage(ERR_get_error());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set maximum SSL version specified: %s\n"),
+ err);
+ return -1;
+ }
+ }
+
/*
* Disable OpenSSL's moving-write-buffer sanity check, because it causes
* unnecessary failures in nonblocking send cases.
@@ -1659,3 +1709,34 @@ PQssl_passwd_cb(char *buf, int size, int rwflag, void *userdata)
else
return PQdefaultSSLKeyPassHook(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 nonnegative value is returned, subsequent code can
+ * assume it's working with a supported version.
+ */
+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.c b/src/interfaces/libpq/fe-secure.c
index 52f6e8790e..fbbdee4686 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -555,3 +555,53 @@ pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe)
}
#endif /* ENABLE_THREAD_SAFETY && !WIN32 */
+
+bool
+pq_verify_ssl_protocol_option(const char *protocolversion)
+{
+ if (!protocolversion || strlen(protocolversion) == 0)
+ return false;
+
+ if (pg_strcasecmp(protocolversion, "TLSv1") == 0
+ || pg_strcasecmp(protocolversion, "TLSv1.1") == 0
+ || pg_strcasecmp(protocolversion, "TLSv1.2") == 0
+ || pg_strcasecmp(protocolversion, "TLSv1.3") == 0)
+ return true;
+
+ return false;
+}
+
+/*
+ * Ensure that the protocol range is sane
+ *
+ * Make sure that the maximum version isn't lower than the minimum. The check
+ * is performed on the input string to keep it TLS backend agnostic. Input to
+ * this function is expected verified with pq_verify_ssl_protocol_option, as
+ * the code is not performing errorchecking on the input.
+ */
+bool
+pq_verify_ssl_protocol_range(const char *min, const char *max)
+{
+ /*
+ * If the minimum version is the lowest one we accept, then all options
+ * for max are valid.
+ */
+ if (strlen(min) == strlen("TLSv1"))
+ return true;
+
+ /*
+ * We know now that the minimum isn't TLSv1, so having that as a max is
+ * not valid.
+ */
+ if (strlen(max) == strlen("TLSv1"))
+ return false;
+
+ /*
+ * At this point we know we have a mix of TLSv1.1 through 1.3 versions, so
+ * we can work with the properties of the minor rev character.
+ */
+ if (*(min + strlen("TLSv1.")) > *(max + strlen("TLSv1.")))
+ return false;
+
+ return true;
+}
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 79bc3780ff..82399c968d 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -367,6 +367,8 @@ struct pg_conn
char *krbsrvname; /* Kerberos service name */
char *gsslib; /* What GSS library to use ("gssapi" or
* "sspi") */
+ char *sslminprotocolversion; /* minimum TLS protocol version */
+ char *sslmaxprotocolversion; /* maximum TLS protocol version */
/* Type of connection to make. Possible values: any, read-write. */
char *target_session_attrs;
@@ -682,6 +684,8 @@ extern ssize_t pqsecure_read(PGconn *, void *ptr, size_t len);
extern ssize_t pqsecure_write(PGconn *, const void *ptr, size_t len);
extern ssize_t pqsecure_raw_read(PGconn *, void *ptr, size_t len);
extern ssize_t pqsecure_raw_write(PGconn *, const void *ptr, size_t len);
+extern bool pq_verify_ssl_protocol_option(const char *protocolversion);
+extern bool pq_verify_ssl_protocol_range(const char *min, const char *max);
#if defined(ENABLE_THREAD_SAFETY) && !defined(WIN32)
extern int pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending);
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 7b18402cf6..2bd81d53cc 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -13,7 +13,7 @@ use SSLServer;
if ($ENV{with_openssl} eq 'yes')
{
- plan tests => 86;
+ plan tests => 93;
}
else
{
@@ -356,6 +356,30 @@ command_like(
^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,_null_,_null_,_null_\r?$}mx,
'pg_stat_ssl view without client certificate');
+# Test min/mix protocol versions
+test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require sslminprotocolversion=tlsv1.2 sslmaxprotocolversion=TLSv1.3",
+ "connect with correct range of allowed TLS protocol versions");
+
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require sslminprotocolversion=TLSv1.3 sslmaxprotocolversion=tlsv1.2",
+ qr/invalid protocol version range/,
+ "connect with an incorrect range of TLS protocol versions leaving no versions allowed");
+
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require sslminprotocolversion=TLSv1.3 sslmaxprotocolversion=tlsv1",
+ qr/invalid protocol version range/,
+ "connect with an incorrect range of TLS protocol versions leaving no versions allowed");
+
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require sslminprotocolversion=TLSv3.1",
+ qr/invalid sslminprotocolversion value/,
+ "connect with an incorrect TLS protocol version");
+
### Server-side tests.
###
### Test certificate authorization.
--
2.21.0 (Apple Git-122.2)
On Fri, Jan 24, 2020 at 12:19:31PM +0100, Daniel Gustafsson wrote:
Attached is a v5 of the patch which hopefully address all the comments raised,
sorry for the delay.
Thanks for the new version.
psql: error: could not connect to server: invalid or unsupported
maximum protocol version specified: TLSv1.3
Running the regression tests with OpenSSL 1.0.1, 1.0.2 or 1.1.0 fails,
because TLSv1.3 (TLS1_3_VERSION) is not supported in those versions.
I think that it is better to just rely on TLSv1.2 for all that,
knowing that the server default for the minimum bound is v1.2.
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require
sslminprotocolversion=TLSv1.3 sslmaxprotocolversion=tlsv1.2",
+ qr/invalid protocol version range/,
+ "connect with an incorrect range of TLS protocol versions
leaving no versions allowed");
+
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require
sslminprotocolversion=TLSv1.3 sslmaxprotocolversion=tlsv1",
+ qr/invalid protocol version range/,
+ "connect with an incorrect range of TLS protocol versions
leaving no versions allowed");
This is testing twice pattern the same thing, and I am not sure if is
is worth bothering about the special case with TLSv1 (using just a
comparison with pg_strcasecmp you don't actually need those special
checks..).
Tests should make sure that a failure happens when an incorrect value
is set for sslminprotocolversion and sslmaxprotocolversion.
For readability, I think that it is better to consider NULL or empty
values for the parameters to be valid. They are actually valid
values, because they just get ignored when creating the connection.
Adding an assertion within the routine for the protocol range check to
make sure that the values are valid makes the code more robust.
+ {"sslminprotocolversion", "PGSSLMINPROTOCOLVERSION", NULL,
NULL,
+ "SSL-Minimum-Protocol-Version", "", /*
sizeof("TLSv1.x") */ 7,
+ offsetof(struct pg_conn, sslminprotocolversion)},
+
+ {"sslmaxprotocolversion", "PGSSLMAXPROTOCOLVERSION", NULL,
NULL,
+ "SSL-Maximum-Protocol-Version", "", /*
sizeof("TLSv1.x") */ 7,
Missing a zero-terminator in the count here. And actually
gssencmode is wrong as well.. I'll report that on a different
thread.
+# Test min/mix protocol versions
Typo here.
+bool
+pq_verify_ssl_protocol_option(const char *protocolversion)
[...]
+bool
+pq_verify_ssl_protocol_range(const char *min, const char *max)
Both routines are just used in fe-connect.c to validate the connection
parameters, so it is better to keep them static and in fe-connect.c in
my opinion.
+ if (*(min + strlen("TLSv1.")) > *(max + strlen("TLSv1.")))
+ return false;
It is enough to use pg_strcasecmp() here.
Hm. I am not sure that having a separate section "Client Protocol
Usage" brings much, so I have removed this one, and added an extra
sentence for the maximum protocol regarding its value for testing or
protocol compatibility.
The regression tests of postgres_fdw failed because of the new
parameters. One update later, they run fine.
So, attached is an updated version of the patch that I have spent a
couple of hours polishing. What do you think?
--
Michael
Attachments:
libpq_minmaxproto_v6.patchtext/x-diff; charset=us-asciiDownload
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 0cc59f1be1..987ab660cb 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -1274,6 +1274,9 @@ X509_NAME_to_cstring(X509_NAME *name)
* version, then we log with the given loglevel and return (if 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, const char *guc_name, int loglevel)
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 80b54bc92b..8498f32f8d 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -320,6 +320,14 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Require-Peer", "", 10,
offsetof(struct pg_conn, requirepeer)},
+ {"sslminprotocolversion", "PGSSLMINPROTOCOLVERSION", NULL, NULL,
+ "SSL-Minimum-Protocol-Version", "", 8, /* sizeof("TLSv1.x") == 8 */
+ offsetof(struct pg_conn, sslminprotocolversion)},
+
+ {"sslmaxprotocolversion", "PGSSLMAXPROTOCOLVERSION", NULL, NULL,
+ "SSL-Maximum-Protocol-Version", "", 8, /* sizeof("TLSv1.x") == 8 */
+ offsetof(struct pg_conn, sslmaxprotocolversion)},
+
/*
* As with SSL, all GSS options are exposed even in builds that don't have
* support.
@@ -426,6 +434,8 @@ static char *passwordFromFile(const char *hostname, const char *port, const char
const char *username, const char *pgpassfile);
static void pgpassfileWarning(PGconn *conn);
static void default_threadlock(int acquire);
+static bool sslVerifyProtocolVersion(const char *version);
+static bool sslVerifyProtocolRange(const char *min, const char *max);
/* global variable because fe-auth.c needs to access it */
@@ -1285,6 +1295,40 @@ connectOptions2(PGconn *conn)
goto oom_error;
}
+ /*
+ * Validate TLS protocol versions for sslminprotocolversion and
+ * sslmaxprotocolversion.
+ */
+ if (!sslVerifyProtocolVersion(conn->sslminprotocolversion))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid sslminprotocolversion value: \"%s\"\n"),
+ conn->sslminprotocolversion);
+ return false;
+ }
+ if (!sslVerifyProtocolVersion(conn->sslmaxprotocolversion))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid sslmaxprotocolversion value: \"%s\"\n"),
+ conn->sslmaxprotocolversion);
+ return false;
+ }
+
+ /*
+ * Check if the range of SSL protocols defined is correct. This is done
+ * at this early step because this is independent of the SSL
+ * implementation used, and this avoids unnecessary cycles with an
+ * already-built SSL context when the connection is being established, as
+ * it would be doomed anyway.
+ */
+ if (!sslVerifyProtocolRange(conn->sslminprotocolversion,
+ conn->sslmaxprotocolversion))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid SSL protocol version range"));
+ return false;
+ }
+
/*
* validate gssencmode option
*/
@@ -4001,6 +4045,10 @@ freePGconn(PGconn *conn)
free(conn->sslcompression);
if (conn->requirepeer)
free(conn->requirepeer);
+ if (conn->sslminprotocolversion)
+ free(conn->sslminprotocolversion);
+ if (conn->sslmaxprotocolversion)
+ free(conn->sslmaxprotocolversion);
if (conn->gssencmode)
free(conn->gssencmode);
if (conn->krbsrvname)
@@ -7031,6 +7079,71 @@ pgpassfileWarning(PGconn *conn)
}
}
+/*
+ * Check if the SSL procotol value given in input is valid or not.
+ * This is used as a sanity check routine for the connection parameters
+ * sslminprotocolversion and sslmaxprotocolversion.
+ */
+static bool
+sslVerifyProtocolVersion(const char *version)
+{
+ /*
+ * An empty string and a NULL value are considered valid as it is
+ * equivalent to ignoring the parameter.
+ */
+ if (!version || strlen(version) == 0)
+ return true;
+
+ if (pg_strcasecmp(version, "TLSv1") == 0 ||
+ pg_strcasecmp(version, "TLSv1.1") == 0 ||
+ pg_strcasecmp(version, "TLSv1.2") == 0 ||
+ pg_strcasecmp(version, "TLSv1.3") == 0)
+ return true;
+
+ /* anything else is wrong */
+ return false;
+}
+
+
+/*
+ * Ensure that the SSL protocol range given in input is correct. The check
+ * is performed on the input string to keep it TLS backend agnostic. Input
+ * to this function is expected verified with sslVerifyProtocolVersion().
+ */
+static bool
+sslVerifyProtocolRange(const char *min, const char *max)
+{
+ Assert(sslVerifyProtocolVersion(min) &&
+ sslVerifyProtocolVersion(max));
+
+ /* If at least one of the bounds is not set, the range is valid */
+ if (min == NULL || max == NULL || strlen(min) == 0 || strlen(max) == 0)
+ return true;
+
+ /*
+ * If the minimum version is the lowest one we accept, then all options
+ * for the maximum are valid.
+ */
+ if (pg_strcasecmp(min, "TLSv1") == 0)
+ return true;
+
+ /*
+ * The minimum bound is valid, and cannot be TLSv1, so using TLSv1 for the
+ * maximum is incorrect.
+ */
+ if (pg_strcasecmp(max, "TLSv1") == 0)
+ return false;
+
+ /*
+ * At this point we know that we have a mix of TLSv1.1 through 1.3
+ * versions.
+ */
+ if (pg_strcasecmp(min, max) > 0)
+ return false;
+
+ return true;
+}
+
/*
* Obtain user's home directory, return in given buffer
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 0e84fc8ac6..acf2addcc9 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -30,6 +30,7 @@
#include "fe-auth.h"
#include "fe-secure-common.h"
#include "libpq-int.h"
+#include "common/openssl.h"
#ifdef WIN32
#include "win32.h"
@@ -95,6 +96,7 @@ static long win32_ssl_create_mutex = 0;
#endif /* ENABLE_THREAD_SAFETY */
static PQsslKeyPassHook_type PQsslKeyPassHook = NULL;
+static int ssl_protocol_version_to_openssl(const char *protocol);
/* ------------------------------------------------------------ */
/* Procedures common to all secure sessions */
@@ -787,6 +789,8 @@ initialize_SSL(PGconn *conn)
bool have_cert;
bool have_rootcert;
EVP_PKEY *pkey = NULL;
+ int ssl_max_ver;
+ int ssl_min_ver;
/*
* We'll need the home directory if any of the relevant parameters are
@@ -843,6 +847,54 @@ initialize_SSL(PGconn *conn)
/* Disable old protocol versions */
SSL_CTX_set_options(SSL_context, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
+ if (conn->sslminprotocolversion &&
+ strlen(conn->sslminprotocolversion) != 0)
+ {
+ ssl_min_ver = ssl_protocol_version_to_openssl(conn->sslminprotocolversion);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->sslminprotocolversion);
+ return -1;
+ }
+
+ if (!SSL_CTX_set_min_proto_version(SSL_context, ssl_min_ver))
+ {
+ char *err = SSLerrmessage(ERR_get_error());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not set minimum version of SSL protocol: %s\n"),
+ err);
+ return -1;
+ }
+ }
+
+ if (conn->sslmaxprotocolversion &&
+ strlen(conn->sslmaxprotocolversion) != 0)
+ {
+ ssl_max_ver = ssl_protocol_version_to_openssl(conn->sslmaxprotocolversion);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->sslmaxprotocolversion);
+ return -1;
+ }
+
+ if (!SSL_CTX_set_max_proto_version(SSL_context, ssl_max_ver))
+ {
+ char *err = SSLerrmessage(ERR_get_error());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not set maximum version of SSL protocol: %s\n"),
+ err);
+ return -1;
+ }
+ }
+
/*
* Disable OpenSSL's moving-write-buffer sanity check, because it causes
* unnecessary failures in nonblocking send cases.
@@ -1659,3 +1711,37 @@ PQssl_passwd_cb(char *buf, int size, int rwflag, void *userdata)
else
return PQdefaultSSLKeyPassHook(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/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 79bc3780ff..72931e6019 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -367,6 +367,8 @@ struct pg_conn
char *krbsrvname; /* Kerberos service name */
char *gsslib; /* What GSS library to use ("gssapi" or
* "sspi") */
+ char *sslminprotocolversion; /* minimum TLS protocol version */
+ char *sslmaxprotocolversion; /* maximum TLS protocol version */
/* Type of connection to make. Possible values: any, read-write. */
char *target_session_attrs;
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 7b18402cf6..6b57b16fab 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -13,7 +13,7 @@ use SSLServer;
if ($ENV{with_openssl} eq 'yes')
{
- plan tests => 86;
+ plan tests => 93;
}
else
{
@@ -356,6 +356,27 @@ command_like(
^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,_null_,_null_,_null_\r?$}mx,
'pg_stat_ssl view without client certificate');
+# Test min/max SSL protocol versions.
+test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require sslminprotocolversion=TLSv1.2 sslmaxprotocolversion=TLSv1.2",
+ "connection success with correct range of TLS protocol versions");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require sslminprotocolversion=TLSv1.2 sslmaxprotocolversion=TLSv1.1",
+ qr/invalid SSL protocol version range/,
+ "connection failure with incorrect range of TLS protocol versions");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require sslminprotocolversion=incorrect_tls",
+ qr/invalid sslminprotocolversion value/,
+ "connection failure with an incorrect SSL protocol minimum bound");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require sslmaxprotocolversion=incorrect_tls",
+ qr/invalid sslmaxprotocolversion value/,
+ "connection failure with an incorrect SSL protocol maximum bound");
+
### Server-side tests.
###
### Test certificate authorization.
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index fcbf7fafbd..9a24c19ccb 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1732,6 +1732,40 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
</listitem>
</varlistentry>
+ <varlistentry id="libpq-connect-sslminprotocolversion" xreflabel="sslminprotocolversion">
+ <term><literal>sslminprotocolversion</literal></term>
+ <listitem>
+ <para>
+ This parameter specifies the minimum SSL/TLS protocol version to allow
+ for the connection. Valid values are <literal>TLSv1</literal>,
+ <literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
+ <literal>TLSv1.3</literal>. The supported protocols depend on the
+ version of <productname>OpenSSL</productname> used, older versions
+ not supporting the most modern protocol versions. If not set, this
+ parameter is ignored and the connection will use the minimum bound
+ defined by the backend.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-connect-sslmaxprotocolversion" xreflabel="sslmaxprotocolversion">
+ <term><literal>sslmaxprotocolversion</literal></term>
+ <listitem>
+ <para>
+ This parameter specifies the maximum SSL/TLS protocol version to allow
+ for the connection. Valid values are <literal>TLSv1</literal>,
+ <literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
+ <literal>TLSv1.3</literal>. The supported protocols depend on the
+ version of <productname>OpenSSL</productname> used, older versions
+ not supporting the most modern protocol versions. If not set, this
+ parameter is ignored and the connection will use the maximum bound
+ defined by the backend, if set. Setting the maximum protocol version
+ is mainly useful for testing or if some component has issues working
+ with a newer protocol.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="libpq-connect-krbsrvname" xreflabel="krbsrvname">
<term><literal>krbsrvname</literal></term>
<listitem>
@@ -7120,6 +7154,26 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
</para>
</listitem>
+ <listitem>
+ <para>
+ <indexterm>
+ <primary><envar>PGSSLMINPROTOCOLVERSION</envar></primary>
+ </indexterm>
+ <envar>PGSSLMINPROTOCOLVERSION</envar> behaves the same as the <xref
+ linkend="libpq-connect-sslminprotocolversion"/> connection parameter.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <indexterm>
+ <primary><envar>PGSSLMAXPROTOCOLVERSION</envar></primary>
+ </indexterm>
+ <envar>PGSSLMAXPROTOCOLVERSION</envar> behaves the same as the <xref
+ linkend="libpq-connect-sslminprotocolversion"/> connection parameter.
+ </para>
+ </listitem>
+
<listitem>
<para>
<indexterm>
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index ebe7bfde23..62c2697920 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8898,7 +8898,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, sslminprotocolversion, sslmaxprotocolversion, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
On 27 Jan 2020, at 07:01, Michael Paquier <michael@paquier.xyz> wrote:
On Fri, Jan 24, 2020 at 12:19:31PM +0100, Daniel Gustafsson wrote:
Attached is a v5 of the patch which hopefully address all the comments raised,
sorry for the delay.Thanks for the new version.
Thanks for review and hackery!
psql: error: could not connect to server: invalid or unsupported
maximum protocol version specified: TLSv1.3
Running the regression tests with OpenSSL 1.0.1, 1.0.2 or 1.1.0 fails,
because TLSv1.3 (TLS1_3_VERSION) is not supported in those versions.
I think that it is better to just rely on TLSv1.2 for all that,
knowing that the server default for the minimum bound is v1.2.
Yes, of course, brainfade on my part.
+ {"sslminprotocolversion", "PGSSLMINPROTOCOLVERSION", NULL, NULL, + "SSL-Minimum-Protocol-Version", "", /* sizeof("TLSv1.x") */ 7, + offsetof(struct pg_conn, sslminprotocolversion)}, + + {"sslmaxprotocolversion", "PGSSLMAXPROTOCOLVERSION", NULL, NULL, + "SSL-Maximum-Protocol-Version", "", /* sizeof("TLSv1.x") */ 7, Missing a zero-terminator in the count here. And actually gssencmode is wrong as well.. I'll report that on a different thread.
Nice catch, I plead guilty to copy-pasting and transferring the error.
+bool +pq_verify_ssl_protocol_option(const char *protocolversion) [...] +bool +pq_verify_ssl_protocol_range(const char *min, const char *max) Both routines are just used in fe-connect.c to validate the connection parameters, so it is better to keep them static and in fe-connect.c in my opinion.
Ok. I prefer to keep the TLS code collected in fe-secure.c, but I don't have
strong enough opinions to kick up a fuzz.
Hm. I am not sure that having a separate section "Client Protocol
Usage" brings much, so I have removed this one, and added an extra
sentence for the maximum protocol regarding its value for testing or
protocol compatibility.
I'm not convinced, this forces the reader to know what to look for (the
connection parameters) rather than being informed. If anything, I think we
need more explanatory sections in the docs.
The regression tests of postgres_fdw failed because of the new
parameters. One update later, they run fine.
Doh, thanks.
So, attached is an updated version of the patch that I have spent a
couple of hours polishing. What do you think?
Overall a +1 on this version, thanks for picking it up!
cheers ./daniel
On Mon, Jan 27, 2020 at 09:49:09AM +0100, Daniel Gustafsson wrote:
On 27 Jan 2020, at 07:01, Michael Paquier <michael@paquier.xyz> wrote:
Ok. I prefer to keep the TLS code collected in fe-secure.c, but I don't have
strong enough opinions to kick up a fuzz.
They are parameter-related, so fe-connect.c made the most sense to me.
The routine checking after the range makes the code more readable IMO
even if we only use it in one place.
Hm. I am not sure that having a separate section "Client Protocol
Usage" brings much, so I have removed this one, and added an extra
sentence for the maximum protocol regarding its value for testing or
protocol compatibility.I'm not convinced, this forces the reader to know what to look for (the
connection parameters) rather than being informed. If anything, I think we
need more explanatory sections in the docs.So, attached is an updated version of the patch that I have spent a
couple of hours polishing. What do you think?Overall a +1 on this version, thanks for picking it up!
Thanks. I have committed the bulk of the changes. As mentioned
previously, I still have doubts about the value of the new section for
the new protocol usage. Once reworded a bit, I finish with the
attached, and the following paragraph for libpq.sgml:
+ <sect2>
+ <title>Client Protocol Usage</title>
+ <para>
+ When connecting using SSL, the client and server negotiate which protocol
+ to use for the connection. <productname>PostgreSQL</productname> supports
+ <literal>TLSv1</literal>, <literal>TLSv1.1</literal>,
+ <literal>TLSv1.2</literal> and <literal>TLSv1.3</literal>, but the
+ protocols available depend on the version of
+ <productname>OpenSSL</productname> that the client and the backend are
+ using. The minimum requested version can be specified with
+ <literal>sslminprotocolversion</literal>, which will ensure that the
+ connection uses that protocol version or higher. The maximum requested
+ version can be specified with <literal>sslmaxprotocolversion</literal>.
+ </para>
+ </sect2>
Now, we already mention in the docs which values the min and max
bounds support, and that the version of OpenSSL used by the backend
and the frontend are impacted by that depending on what version of
OpenSSL one or the other link to. The remaining piece is that the
client and the server negotiate the protocol they use, which is an
obvious fact, at least to me..
--
Michael
Attachments:
libpq_minmaxproto_doc.patchtext/x-diff; charset=us-asciiDownload
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 9a24c19ccb..4f1a41d145 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -7847,6 +7847,22 @@ ldap://ldap.acme.com/cn=dbserver,cn=hosts?pgconnectinfo?base?(objectclass=*)
</sect2>
+ <sect2>
+ <title>Client Protocol Usage</title>
+ <para>
+ When connecting using SSL, the client and server negotiate which protocol
+ to use for the connection. <productname>PostgreSQL</productname> supports
+ <literal>TLSv1</literal>, <literal>TLSv1.1</literal>,
+ <literal>TLSv1.2</literal> and <literal>TLSv1.3</literal>, but the
+ protocols available depend on the version of
+ <productname>OpenSSL</productname> that the client and the backend are
+ using. The minimum requested version can be specified with
+ <literal>sslminprotocolversion</literal>, which will ensure that the
+ connection uses that protocol version or higher. The maximum requested
+ version can be specified with <literal>sslmaxprotocolversion</literal>.
+ </para>
+ </sect2>
+
<sect2 id="libpq-ssl-fileusage">
<title>SSL Client File Usage</title>
On 28 Jan 2020, at 04:53, Michael Paquier <michael@paquier.xyz> wrote:
Now, we already mention in the docs which values the min and max
bounds support, and that the version of OpenSSL used by the backend
and the frontend are impacted by that depending on what version of
OpenSSL one or the other link to. The remaining piece is that the
client and the server negotiate the protocol they use, which is an
obvious fact, at least to me..
You don't really qualify as the target audience for basic, yet not always
universally known/understood, sections in the documentation though =) I've
heard enough complaints that it's complicated to set up that I think we need to
make the docs more digestable, but if noone else +1's then lets drop it.
cheers ./daniel
On Tue, Jan 28, 2020 at 11:29:39AM +0100, Daniel Gustafsson wrote:
You don't really qualify as the target audience for basic, yet not always
universally known/understood, sections in the documentation though =)
Likely I don't.
I've heard enough complaints that it's complicated to set up that I
think we need to make the docs more digestable, but if noone else
+1's then lets drop it.
Sure. Now I am pretty sure that we would need a bit more than just
saying that the SSL protocol is negotiated between the backend and
libpq if we add a new section.
--
Michael
Can we reconsider whether we really want to name the new settings like
"sslminprotocolversion", or whether we could add some underscores, both
for readability and for consistency with the server-side options?
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On 24 Apr 2020, at 12:56, Peter Eisentraut <peter.eisentraut@2ndquadrant.com> wrote:
Can we reconsider whether we really want to name the new settings like "sslminprotocolversion", or whether we could add some underscores, both for readability and for consistency with the server-side options?
That was brought up by Michael in the thread, but none of us followed up on it
it seems. The current name was chosen to be consistent with the already
existing ssl* client-side settings, but I don't really have strong opinions on
if that makes sense or not. Perhaps use ssl_m{in|max}_protocolversion to make
it more readable?
The attached renames the userfacing setting, but keeps the environment variable
without underscores as most settings have env vars without underscores.
cheers ./daniel
Attachments:
minmaxproto_naming.patchapplication/octet-stream; name=minmaxproto_naming.patch; x-unix-mode=0644Download
From 9188ef57781b09be9aea05860726bae7ce12840a Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Fri, 24 Apr 2020 13:49:11 +0200
Subject: [PATCH] Rename client-side TLS protocol settings
Improve readability of client side TLS protocol settings by adding
underscores to break up the really long word. The environment vars
are kept without underscores to be consistent with (most) other
env vars.
Reported-by: Peter Eisentraut
---
contrib/postgres_fdw/expected/postgres_fdw.out | 2 +-
doc/src/sgml/libpq.sgml | 4 ++--
src/interfaces/libpq/fe-connect.c | 14 +++++++-------
src/test/ssl/t/001_ssltests.pl | 12 ++++++------
4 files changed, 16 insertions(+), 16 deletions(-)
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 62c2697920..7ae070fbeb 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8898,7 +8898,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, sslminprotocolversion, sslmaxprotocolversion, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocolversion, ssl_max_protocolversion, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 75d2224a61..51cbf4b3a5 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1737,7 +1737,7 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
</varlistentry>
<varlistentry id="libpq-connect-sslminprotocolversion" xreflabel="sslminprotocolversion">
- <term><literal>sslminprotocolversion</literal></term>
+ <term><literal>ssl_min_protocolversion</literal></term>
<listitem>
<para>
This parameter specifies the minimum SSL/TLS protocol version to allow
@@ -1753,7 +1753,7 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
</varlistentry>
<varlistentry id="libpq-connect-sslmaxprotocolversion" xreflabel="sslmaxprotocolversion">
- <term><literal>sslmaxprotocolversion</literal></term>
+ <term><literal>ssl_max_protocolversion</literal></term>
<listitem>
<para>
This parameter specifies the maximum SSL/TLS protocol version to allow
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 0157c619aa..e2882b9f71 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -320,11 +320,11 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Require-Peer", "", 10,
offsetof(struct pg_conn, requirepeer)},
- {"sslminprotocolversion", "PGSSLMINPROTOCOLVERSION", NULL, NULL,
+ {"ssl_min_protocolversion", "PGSSLMINPROTOCOLVERSION", NULL, NULL,
"SSL-Minimum-Protocol-Version", "", 8, /* sizeof("TLSv1.x") == 8 */
offsetof(struct pg_conn, sslminprotocolversion)},
- {"sslmaxprotocolversion", "PGSSLMAXPROTOCOLVERSION", NULL, NULL,
+ {"ssl_max_protocolversion", "PGSSLMAXPROTOCOLVERSION", NULL, NULL,
"SSL-Maximum-Protocol-Version", "", 8, /* sizeof("TLSv1.x") == 8 */
offsetof(struct pg_conn, sslmaxprotocolversion)},
@@ -1301,14 +1301,14 @@ connectOptions2(PGconn *conn)
}
/*
- * Validate TLS protocol versions for sslminprotocolversion and
- * sslmaxprotocolversion.
+ * Validate TLS protocol versions for ssl_min_protocolversion and
+ * ssl_max_protocolversion.
*/
if (!sslVerifyProtocolVersion(conn->sslminprotocolversion))
{
conn->status = CONNECTION_BAD;
printfPQExpBuffer(&conn->errorMessage,
- libpq_gettext("invalid sslminprotocolversion value: \"%s\"\n"),
+ libpq_gettext("invalid ssl_min_protocolversion value: \"%s\"\n"),
conn->sslminprotocolversion);
return false;
}
@@ -1316,7 +1316,7 @@ connectOptions2(PGconn *conn)
{
conn->status = CONNECTION_BAD;
printfPQExpBuffer(&conn->errorMessage,
- libpq_gettext("invalid sslmaxprotocolversion value: \"%s\"\n"),
+ libpq_gettext("invalid ssl_max_protocolversion value: \"%s\"\n"),
conn->sslmaxprotocolversion);
return false;
}
@@ -7120,7 +7120,7 @@ pgpassfileWarning(PGconn *conn)
/*
* Check if the SSL procotol value given in input is valid or not.
* This is used as a sanity check routine for the connection parameters
- * sslminprotocolversion and sslmaxprotocolversion.
+ * ssl_min_protocolversion and ssl_max_protocolversion.
*/
static bool
sslVerifyProtocolVersion(const char *version)
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index d035ac7fc9..31b4e56587 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -357,22 +357,22 @@ command_like(
# Test min/max SSL protocol versions.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require sslminprotocolversion=TLSv1.2 sslmaxprotocolversion=TLSv1.2",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocolversion=TLSv1.2 ssl_max_protocolversion=TLSv1.2",
"connection success with correct range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require sslminprotocolversion=TLSv1.2 sslmaxprotocolversion=TLSv1.1",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocolversion=TLSv1.2 ssl_max_protocolversion=TLSv1.1",
qr/invalid SSL protocol version range/,
"connection failure with incorrect range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require sslminprotocolversion=incorrect_tls",
- qr/invalid sslminprotocolversion value/,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocolversion=incorrect_tls",
+ qr/invalid ssl_min_protocolversion value/,
"connection failure with an incorrect SSL protocol minimum bound");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require sslmaxprotocolversion=incorrect_tls",
- qr/invalid sslmaxprotocolversion value/,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocolversion=incorrect_tls",
+ qr/invalid ssl_max_protocolversion value/,
"connection failure with an incorrect SSL protocol maximum bound");
### Server-side tests.
--
2.21.1 (Apple Git-122.3)
On Fri, Apr 24, 2020 at 02:03:04PM +0200, Daniel Gustafsson wrote:
That was brought up by Michael in the thread, but none of us followed up on it
it seems. The current name was chosen to be consistent with the already
existing ssl* client-side settings, but I don't really have strong opinions on
if that makes sense or not. Perhaps use ssl_m{in|max}_protocolversion to make
it more readable?
There was no hard push in favor of this comment so I did not insist,
but I am not wedded to the existing connection parameter names.
- {"sslminprotocolversion", "PGSSLMINPROTOCOLVERSION", NULL, NULL,
+ {"ssl_min_protocolversion", "PGSSLMINPROTOCOLVERSION", NULL, NULL,
Shouldn't that actually be "ssl_min_protocol_version" with one extra
underscore?
The attached renames the userfacing setting, but keeps the environment variable
without underscores as most settings have env vars without underscores.
There are two in this case: PG_COLOR and PG_COLORS. For readability
it could make sense to use something like PG_SSL_MIN_PROTOCOL_VERSION
or PGSSL_MIN_PROTOCOL_VERSION, but like Daniel I'd rather keep the env
variables without underscores.
--
Michael
On 2020-04-24 14:03, Daniel Gustafsson wrote:
On 24 Apr 2020, at 12:56, Peter Eisentraut <peter.eisentraut@2ndquadrant.com> wrote:
Can we reconsider whether we really want to name the new settings like "sslminprotocolversion", or whether we could add some underscores, both for readability and for consistency with the server-side options?
That was brought up by Michael in the thread, but none of us followed up on it
it seems. The current name was chosen to be consistent with the already
existing ssl* client-side settings, but I don't really have strong opinions on
if that makes sense or not. Perhaps use ssl_m{in|max}_protocolversion to make
it more readable?
The names on the backend side are ssl_{min|max|_protocol_version.
The attached renames the userfacing setting, but keeps the environment variable
without underscores as most settings have env vars without underscores.
Keeping the environment variable as is seems fine (also consistent with
"channel_binding").
I would, however, prefer to also rename the internal symbols.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On 26 Apr 2020, at 14:01, Peter Eisentraut <peter.eisentraut@2ndquadrant.com> wrote:
On 2020-04-24 14:03, Daniel Gustafsson wrote:
On 24 Apr 2020, at 12:56, Peter Eisentraut <peter.eisentraut@2ndquadrant.com> wrote:
Can we reconsider whether we really want to name the new settings like "sslminprotocolversion", or whether we could add some underscores, both for readability and for consistency with the server-side options?
That was brought up by Michael in the thread, but none of us followed up on it
it seems. The current name was chosen to be consistent with the already
existing ssl* client-side settings, but I don't really have strong opinions on
if that makes sense or not. Perhaps use ssl_m{in|max}_protocolversion to make
it more readable?The names on the backend side are ssl_{min|max|_protocol_version.
That was the preferred name by Michael too elsewhere in the thread, so went
ahead and made it so.
The attached renames the userfacing setting, but keeps the environment variable
without underscores as most settings have env vars without underscores.Keeping the environment variable as is seems fine (also consistent with "channel_binding").
I would, however, prefer to also rename the internal symbols.
Done in the attached v2.
cheers ./daniel
Attachments:
minmaxproto_naming-v2.patchapplication/octet-stream; name=minmaxproto_naming-v2.patch; x-unix-mode=0644Download
From 1b589521d39ff175f7b080ad1174e0f8a90bb1b0 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Fri, 24 Apr 2020 13:49:11 +0200
Subject: [PATCH] Rename client-side TLS protocol settings
The names of the client side TLS protocol settings were originally
chosen to be consistent with the already existing ssl* settings.
This made the names confusingly low however, and inconsistent with
the corresponding TLS protocol version settings in the backend. This
improves readability of client side TLS protocol settings by adding
underscores to break up the really long word. The environment vars
are kept without underscores to be consistent with (most) other
env vars.
Reported-by: Peter Eisentraut
---
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
doc/src/sgml/libpq.sgml | 12 +++---
src/interfaces/libpq/fe-connect.c | 38 +++++++++----------
src/interfaces/libpq/fe-secure-openssl.c | 16 ++++----
src/interfaces/libpq/libpq-int.h | 4 +-
src/test/ssl/t/001_ssltests.pl | 12 +++---
6 files changed, 42 insertions(+), 42 deletions(-)
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 62c2697920..90db550b92 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8898,7 +8898,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, sslminprotocolversion, sslmaxprotocolversion, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 75d2224a61..79c38210a4 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1736,8 +1736,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
</listitem>
</varlistentry>
- <varlistentry id="libpq-connect-sslminprotocolversion" xreflabel="sslminprotocolversion">
- <term><literal>sslminprotocolversion</literal></term>
+ <varlistentry id="libpq-connect-ssl-min-protocol-version" xreflabel="ssl-min-protocol-version">
+ <term><literal>ssl_min_protocol_version</literal></term>
<listitem>
<para>
This parameter specifies the minimum SSL/TLS protocol version to allow
@@ -1752,8 +1752,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
</listitem>
</varlistentry>
- <varlistentry id="libpq-connect-sslmaxprotocolversion" xreflabel="sslmaxprotocolversion">
- <term><literal>sslmaxprotocolversion</literal></term>
+ <varlistentry id="libpq-connect-ssl-max-protocol-version" xreflabel="ssl-max-protocol-version">
+ <term><literal>ssl_max_protocol_version</literal></term>
<listitem>
<para>
This parameter specifies the maximum SSL/TLS protocol version to allow
@@ -7164,7 +7164,7 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
<primary><envar>PGSSLMINPROTOCOLVERSION</envar></primary>
</indexterm>
<envar>PGSSLMINPROTOCOLVERSION</envar> behaves the same as the <xref
- linkend="libpq-connect-sslminprotocolversion"/> connection parameter.
+ linkend="libpq-connect-ssl-min-protocol-version"/> connection parameter.
</para>
</listitem>
@@ -7174,7 +7174,7 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
<primary><envar>PGSSLMAXPROTOCOLVERSION</envar></primary>
</indexterm>
<envar>PGSSLMAXPROTOCOLVERSION</envar> behaves the same as the <xref
- linkend="libpq-connect-sslminprotocolversion"/> connection parameter.
+ linkend="libpq-connect-ssl-min-protocol-version"/> connection parameter.
</para>
</listitem>
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 0157c619aa..23be9a66cb 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -320,13 +320,13 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Require-Peer", "", 10,
offsetof(struct pg_conn, requirepeer)},
- {"sslminprotocolversion", "PGSSLMINPROTOCOLVERSION", NULL, NULL,
+ {"ssl_min_protocol_version", "PGSSLMINPROTOCOLVERSION", NULL, NULL,
"SSL-Minimum-Protocol-Version", "", 8, /* sizeof("TLSv1.x") == 8 */
- offsetof(struct pg_conn, sslminprotocolversion)},
+ offsetof(struct pg_conn, ssl_min_protocol_version)},
- {"sslmaxprotocolversion", "PGSSLMAXPROTOCOLVERSION", NULL, NULL,
+ {"ssl_max_protocol_version", "PGSSLMAXPROTOCOLVERSION", NULL, NULL,
"SSL-Maximum-Protocol-Version", "", 8, /* sizeof("TLSv1.x") == 8 */
- offsetof(struct pg_conn, sslmaxprotocolversion)},
+ offsetof(struct pg_conn, ssl_max_protocol_version)},
/*
* As with SSL, all GSS options are exposed even in builds that don't have
@@ -1301,23 +1301,23 @@ connectOptions2(PGconn *conn)
}
/*
- * Validate TLS protocol versions for sslminprotocolversion and
- * sslmaxprotocolversion.
+ * Validate TLS protocol versions for ssl_min_protocol_version and
+ * ssl_max_protocol_version.
*/
- if (!sslVerifyProtocolVersion(conn->sslminprotocolversion))
+ if (!sslVerifyProtocolVersion(conn->ssl_min_protocol_version))
{
conn->status = CONNECTION_BAD;
printfPQExpBuffer(&conn->errorMessage,
- libpq_gettext("invalid sslminprotocolversion value: \"%s\"\n"),
- conn->sslminprotocolversion);
+ libpq_gettext("invalid ssl_min_protocol_version value: \"%s\"\n"),
+ conn->ssl_min_protocol_version);
return false;
}
- if (!sslVerifyProtocolVersion(conn->sslmaxprotocolversion))
+ if (!sslVerifyProtocolVersion(conn->ssl_max_protocol_version))
{
conn->status = CONNECTION_BAD;
printfPQExpBuffer(&conn->errorMessage,
- libpq_gettext("invalid sslmaxprotocolversion value: \"%s\"\n"),
- conn->sslmaxprotocolversion);
+ libpq_gettext("invalid ssl_max_protocol_version value: \"%s\"\n"),
+ conn->ssl_max_protocol_version);
return false;
}
@@ -1328,8 +1328,8 @@ connectOptions2(PGconn *conn)
* already-built SSL context when the connection is being established, as
* it would be doomed anyway.
*/
- if (!sslVerifyProtocolRange(conn->sslminprotocolversion,
- conn->sslmaxprotocolversion))
+ if (!sslVerifyProtocolRange(conn->ssl_min_protocol_version,
+ conn->ssl_max_protocol_version))
{
conn->status = CONNECTION_BAD;
printfPQExpBuffer(&conn->errorMessage,
@@ -4046,10 +4046,10 @@ freePGconn(PGconn *conn)
free(conn->sslcompression);
if (conn->requirepeer)
free(conn->requirepeer);
- if (conn->sslminprotocolversion)
- free(conn->sslminprotocolversion);
- if (conn->sslmaxprotocolversion)
- free(conn->sslmaxprotocolversion);
+ if (conn->ssl_min_protocol_version)
+ free(conn->ssl_min_protocol_version);
+ if (conn->ssl_max_protocol_version)
+ free(conn->ssl_max_protocol_version);
if (conn->gssencmode)
free(conn->gssencmode);
if (conn->krbsrvname)
@@ -7120,7 +7120,7 @@ pgpassfileWarning(PGconn *conn)
/*
* Check if the SSL procotol value given in input is valid or not.
* This is used as a sanity check routine for the connection parameters
- * sslminprotocolversion and sslmaxprotocolversion.
+ * ssl_min_protocol_version and ssl_max_protocol_version.
*/
static bool
sslVerifyProtocolVersion(const char *version)
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 731aa23c55..ddeeb606f5 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -842,18 +842,18 @@ initialize_SSL(PGconn *conn)
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->sslminprotocolversion &&
- strlen(conn->sslminprotocolversion) != 0)
+ 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->sslminprotocolversion);
+ ssl_min_ver = ssl_protocol_version_to_openssl(conn->ssl_min_protocol_version);
if (ssl_min_ver == -1)
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
- conn->sslminprotocolversion);
+ conn->ssl_min_protocol_version);
SSL_CTX_free(SSL_context);
return -1;
}
@@ -871,18 +871,18 @@ initialize_SSL(PGconn *conn)
}
}
- if (conn->sslmaxprotocolversion &&
- strlen(conn->sslmaxprotocolversion) != 0)
+ 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->sslmaxprotocolversion);
+ ssl_max_ver = ssl_protocol_version_to_openssl(conn->ssl_max_protocol_version);
if (ssl_max_ver == -1)
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
- conn->sslmaxprotocolversion);
+ conn->ssl_max_protocol_version);
SSL_CTX_free(SSL_context);
return -1;
}
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 72931e6019..1de91ae295 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -367,8 +367,8 @@ struct pg_conn
char *krbsrvname; /* Kerberos service name */
char *gsslib; /* What GSS library to use ("gssapi" or
* "sspi") */
- char *sslminprotocolversion; /* minimum TLS protocol version */
- char *sslmaxprotocolversion; /* maximum TLS protocol version */
+ char *ssl_min_protocol_version; /* minimum TLS protocol version */
+ char *ssl_max_protocol_version; /* maximum TLS protocol version */
/* Type of connection to make. Possible values: any, read-write. */
char *target_session_attrs;
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index d035ac7fc9..3e68a49ca9 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -357,22 +357,22 @@ command_like(
# Test min/max SSL protocol versions.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require sslminprotocolversion=TLSv1.2 sslmaxprotocolversion=TLSv1.2",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
"connection success with correct range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require sslminprotocolversion=TLSv1.2 sslmaxprotocolversion=TLSv1.1",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
qr/invalid SSL protocol version range/,
"connection failure with incorrect range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require sslminprotocolversion=incorrect_tls",
- qr/invalid sslminprotocolversion value/,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ qr/invalid ssl_min_protocol_version value/,
"connection failure with an incorrect SSL protocol minimum bound");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require sslmaxprotocolversion=incorrect_tls",
- qr/invalid sslmaxprotocolversion value/,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ qr/invalid ssl_max_protocol_version value/,
"connection failure with an incorrect SSL protocol maximum bound");
### Server-side tests.
--
2.21.1 (Apple Git-122.3)
On Sun, Apr 26, 2020 at 11:20:01PM +0200, Daniel Gustafsson wrote:
That was the preferred name by Michael too elsewhere in the thread, so went
ahead and made it so.
Thanks Daniel.
I would, however, prefer to also rename the internal symbols.
Done in the attached v2.
What you have here looks fine to me. Peter, what do you think?
--
Michael
On 2020-04-27 07:45, Michael Paquier wrote:
On Sun, Apr 26, 2020 at 11:20:01PM +0200, Daniel Gustafsson wrote:
That was the preferred name by Michael too elsewhere in the thread, so went
ahead and made it so.Thanks Daniel.
I would, however, prefer to also rename the internal symbols.
Done in the attached v2.
What you have here looks fine to me. Peter, what do you think?
This looks good to me, except that
xreflabel="ssl-min-protocol-version"
etc. needs to be changed to use underscores.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On Wed, Apr 29, 2020 at 10:33:26PM +0200, Peter Eisentraut wrote:
This looks good to me, except that
xreflabel="ssl-min-protocol-version"
etc. needs to be changed to use underscores.
Indeed, thanks. I have fixed this part and applied the patch.
--
Michael