Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit ff8ca5f

Browse files
committed
Add connection parameters to control SSL protocol min/max in libpq
These two new parameters, named sslminprotocolversion and sslmaxprotocolversion, allow to respectively control the minimum and the maximum version of the SSL protocol used for the SSL connection attempt. The default setting is to allow any version for both the minimum and the maximum bounds, causing libpq to rely on the bounds set by the backend when negotiating the protocol to use for an SSL connection. The bounds are checked when the values are set at the earliest stage possible as this makes the checks independent of any SSL implementation. Author: Daniel Gustafsson Reviewed-by: Michael Paquier, Cary Huang Discussion: https://postgr.es/m/4F246AE3-A7AE-471E-BD3D-C799D3748E03@yesql.se
1 parent 6f38d4d commit ff8ca5f

File tree

7 files changed

+284
-2
lines changed

7 files changed

+284
-2
lines changed

contrib/postgres_fdw/expected/postgres_fdw.out

+1-1
Original file line numberDiff line numberDiff line change
@@ -8898,7 +8898,7 @@ DO $d$
88988898
END;
88998899
$d$;
89008900
ERROR: invalid option "password"
8901-
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
8901+
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
89028902
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
89038903
PL/pgSQL function inline_code_block line 3 at EXECUTE
89048904
-- If we add a password for our user mapping instead, we should get a different

doc/src/sgml/libpq.sgml

+54
Original file line numberDiff line numberDiff line change
@@ -1732,6 +1732,40 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
17321732
</listitem>
17331733
</varlistentry>
17341734

1735+
<varlistentry id="libpq-connect-sslminprotocolversion" xreflabel="sslminprotocolversion">
1736+
<term><literal>sslminprotocolversion</literal></term>
1737+
<listitem>
1738+
<para>
1739+
This parameter specifies the minimum SSL/TLS protocol version to allow
1740+
for the connection. Valid values are <literal>TLSv1</literal>,
1741+
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
1742+
<literal>TLSv1.3</literal>. The supported protocols depend on the
1743+
version of <productname>OpenSSL</productname> used, older versions
1744+
not supporting the most modern protocol versions. If not set, this
1745+
parameter is ignored and the connection will use the minimum bound
1746+
defined by the backend.
1747+
</para>
1748+
</listitem>
1749+
</varlistentry>
1750+
1751+
<varlistentry id="libpq-connect-sslmaxprotocolversion" xreflabel="sslmaxprotocolversion">
1752+
<term><literal>sslmaxprotocolversion</literal></term>
1753+
<listitem>
1754+
<para>
1755+
This parameter specifies the maximum SSL/TLS protocol version to allow
1756+
for the connection. Valid values are <literal>TLSv1</literal>,
1757+
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
1758+
<literal>TLSv1.3</literal>. The supported protocols depend on the
1759+
version of <productname>OpenSSL</productname> used, older versions
1760+
not supporting the most modern protocol versions. If not set, this
1761+
parameter is ignored and the connection will use the maximum bound
1762+
defined by the backend, if set. Setting the maximum protocol version
1763+
is mainly useful for testing or if some component has issues working
1764+
with a newer protocol.
1765+
</para>
1766+
</listitem>
1767+
</varlistentry>
1768+
17351769
<varlistentry id="libpq-connect-krbsrvname" xreflabel="krbsrvname">
17361770
<term><literal>krbsrvname</literal></term>
17371771
<listitem>
@@ -7120,6 +7154,26 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
71207154
</para>
71217155
</listitem>
71227156

7157+
<listitem>
7158+
<para>
7159+
<indexterm>
7160+
<primary><envar>PGSSLMINPROTOCOLVERSION</envar></primary>
7161+
</indexterm>
7162+
<envar>PGSSLMINPROTOCOLVERSION</envar> behaves the same as the <xref
7163+
linkend="libpq-connect-sslminprotocolversion"/> connection parameter.
7164+
</para>
7165+
</listitem>
7166+
7167+
<listitem>
7168+
<para>
7169+
<indexterm>
7170+
<primary><envar>PGSSLMAXPROTOCOLVERSION</envar></primary>
7171+
</indexterm>
7172+
<envar>PGSSLMAXPROTOCOLVERSION</envar> behaves the same as the <xref
7173+
linkend="libpq-connect-sslminprotocolversion"/> connection parameter.
7174+
</para>
7175+
</listitem>
7176+
71237177
<listitem>
71247178
<para>
71257179
<indexterm>

src/backend/libpq/be-secure-openssl.c

+3
Original file line numberDiff line numberDiff line change
@@ -1274,6 +1274,9 @@ X509_NAME_to_cstring(X509_NAME *name)
12741274
* version, then we log with the given loglevel and return (if we return) -1.
12751275
* If a nonnegative value is returned, subsequent code can assume it's working
12761276
* with a supported version.
1277+
*
1278+
* Note: this is rather similar to libpq's routine in fe-secure-openssl.c,
1279+
* so make sure to update both routines if changing this one.
12771280
*/
12781281
static int
12791282
ssl_protocol_version_to_openssl(int v, const char *guc_name, int loglevel)

src/interfaces/libpq/fe-connect.c

+113
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,14 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
320320
"Require-Peer", "", 10,
321321
offsetof(struct pg_conn, requirepeer)},
322322

323+
{"sslminprotocolversion", "PGSSLMINPROTOCOLVERSION", NULL, NULL,
324+
"SSL-Minimum-Protocol-Version", "", 8, /* sizeof("TLSv1.x") == 8 */
325+
offsetof(struct pg_conn, sslminprotocolversion)},
326+
327+
{"sslmaxprotocolversion", "PGSSLMAXPROTOCOLVERSION", NULL, NULL,
328+
"SSL-Maximum-Protocol-Version", "", 8, /* sizeof("TLSv1.x") == 8 */
329+
offsetof(struct pg_conn, sslmaxprotocolversion)},
330+
323331
/*
324332
* As with SSL, all GSS options are exposed even in builds that don't have
325333
* support.
@@ -426,6 +434,8 @@ static char *passwordFromFile(const char *hostname, const char *port, const char
426434
const char *username, const char *pgpassfile);
427435
static void pgpassfileWarning(PGconn *conn);
428436
static void default_threadlock(int acquire);
437+
static bool sslVerifyProtocolVersion(const char *version);
438+
static bool sslVerifyProtocolRange(const char *min, const char *max);
429439

430440

431441
/* global variable because fe-auth.c needs to access it */
@@ -1285,6 +1295,40 @@ connectOptions2(PGconn *conn)
12851295
goto oom_error;
12861296
}
12871297

1298+
/*
1299+
* Validate TLS protocol versions for sslminprotocolversion and
1300+
* sslmaxprotocolversion.
1301+
*/
1302+
if (!sslVerifyProtocolVersion(conn->sslminprotocolversion))
1303+
{
1304+
printfPQExpBuffer(&conn->errorMessage,
1305+
libpq_gettext("invalid sslminprotocolversion value: \"%s\"\n"),
1306+
conn->sslminprotocolversion);
1307+
return false;
1308+
}
1309+
if (!sslVerifyProtocolVersion(conn->sslmaxprotocolversion))
1310+
{
1311+
printfPQExpBuffer(&conn->errorMessage,
1312+
libpq_gettext("invalid sslmaxprotocolversion value: \"%s\"\n"),
1313+
conn->sslmaxprotocolversion);
1314+
return false;
1315+
}
1316+
1317+
/*
1318+
* Check if the range of SSL protocols defined is correct. This is done
1319+
* at this early step because this is independent of the SSL
1320+
* implementation used, and this avoids unnecessary cycles with an
1321+
* already-built SSL context when the connection is being established, as
1322+
* it would be doomed anyway.
1323+
*/
1324+
if (!sslVerifyProtocolRange(conn->sslminprotocolversion,
1325+
conn->sslmaxprotocolversion))
1326+
{
1327+
printfPQExpBuffer(&conn->errorMessage,
1328+
libpq_gettext("invalid SSL protocol version range"));
1329+
return false;
1330+
}
1331+
12881332
/*
12891333
* validate gssencmode option
12901334
*/
@@ -4001,6 +4045,10 @@ freePGconn(PGconn *conn)
40014045
free(conn->sslcompression);
40024046
if (conn->requirepeer)
40034047
free(conn->requirepeer);
4048+
if (conn->sslminprotocolversion)
4049+
free(conn->sslminprotocolversion);
4050+
if (conn->sslmaxprotocolversion)
4051+
free(conn->sslmaxprotocolversion);
40044052
if (conn->gssencmode)
40054053
free(conn->gssencmode);
40064054
if (conn->krbsrvname)
@@ -7031,6 +7079,71 @@ pgpassfileWarning(PGconn *conn)
70317079
}
70327080
}
70337081

7082+
/*
7083+
* Check if the SSL procotol value given in input is valid or not.
7084+
* This is used as a sanity check routine for the connection parameters
7085+
* sslminprotocolversion and sslmaxprotocolversion.
7086+
*/
7087+
static bool
7088+
sslVerifyProtocolVersion(const char *version)
7089+
{
7090+
/*
7091+
* An empty string and a NULL value are considered valid as it is
7092+
* equivalent to ignoring the parameter.
7093+
*/
7094+
if (!version || strlen(version) == 0)
7095+
return true;
7096+
7097+
if (pg_strcasecmp(version, "TLSv1") == 0 ||
7098+
pg_strcasecmp(version, "TLSv1.1") == 0 ||
7099+
pg_strcasecmp(version, "TLSv1.2") == 0 ||
7100+
pg_strcasecmp(version, "TLSv1.3") == 0)
7101+
return true;
7102+
7103+
/* anything else is wrong */
7104+
return false;
7105+
}
7106+
7107+
7108+
/*
7109+
* Ensure that the SSL protocol range given in input is correct. The check
7110+
* is performed on the input string to keep it TLS backend agnostic. Input
7111+
* to this function is expected verified with sslVerifyProtocolVersion().
7112+
*/
7113+
static bool
7114+
sslVerifyProtocolRange(const char *min, const char *max)
7115+
{
7116+
Assert(sslVerifyProtocolVersion(min) &&
7117+
sslVerifyProtocolVersion(max));
7118+
7119+
/* If at least one of the bounds is not set, the range is valid */
7120+
if (min == NULL || max == NULL || strlen(min) == 0 || strlen(max) == 0)
7121+
return true;
7122+
7123+
/*
7124+
* If the minimum version is the lowest one we accept, then all options
7125+
* for the maximum are valid.
7126+
*/
7127+
if (pg_strcasecmp(min, "TLSv1") == 0)
7128+
return true;
7129+
7130+
/*
7131+
* The minimum bound is valid, and cannot be TLSv1, so using TLSv1 for the
7132+
* maximum is incorrect.
7133+
*/
7134+
if (pg_strcasecmp(max, "TLSv1") == 0)
7135+
return false;
7136+
7137+
/*
7138+
* At this point we know that we have a mix of TLSv1.1 through 1.3
7139+
* versions.
7140+
*/
7141+
if (pg_strcasecmp(min, max) > 0)
7142+
return false;
7143+
7144+
return true;
7145+
}
7146+
70347147

70357148
/*
70367149
* Obtain user's home directory, return in given buffer

src/interfaces/libpq/fe-secure-openssl.c

+89
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include "fe-auth.h"
3131
#include "fe-secure-common.h"
3232
#include "libpq-int.h"
33+
#include "common/openssl.h"
3334

3435
#ifdef WIN32
3536
#include "win32.h"
@@ -95,6 +96,7 @@ static long win32_ssl_create_mutex = 0;
9596
#endif /* ENABLE_THREAD_SAFETY */
9697

9798
static PQsslKeyPassHook_type PQsslKeyPassHook = NULL;
99+
static int ssl_protocol_version_to_openssl(const char *protocol);
98100

99101
/* ------------------------------------------------------------ */
100102
/* Procedures common to all secure sessions */
@@ -843,6 +845,59 @@ initialize_SSL(PGconn *conn)
843845
/* Disable old protocol versions */
844846
SSL_CTX_set_options(SSL_context, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
845847

848+
/* Set the minimum and maximum protocol versions if necessary */
849+
if (conn->sslminprotocolversion &&
850+
strlen(conn->sslminprotocolversion) != 0)
851+
{
852+
int ssl_min_ver;
853+
854+
ssl_min_ver = ssl_protocol_version_to_openssl(conn->sslminprotocolversion);
855+
856+
if (ssl_min_ver == -1)
857+
{
858+
printfPQExpBuffer(&conn->errorMessage,
859+
libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
860+
conn->sslminprotocolversion);
861+
return -1;
862+
}
863+
864+
if (!SSL_CTX_set_min_proto_version(SSL_context, ssl_min_ver))
865+
{
866+
char *err = SSLerrmessage(ERR_get_error());
867+
868+
printfPQExpBuffer(&conn->errorMessage,
869+
libpq_gettext("could not set minimum version of SSL protocol: %s\n"),
870+
err);
871+
return -1;
872+
}
873+
}
874+
875+
if (conn->sslmaxprotocolversion &&
876+
strlen(conn->sslmaxprotocolversion) != 0)
877+
{
878+
int ssl_max_ver;
879+
880+
ssl_max_ver = ssl_protocol_version_to_openssl(conn->sslmaxprotocolversion);
881+
882+
if (ssl_max_ver == -1)
883+
{
884+
printfPQExpBuffer(&conn->errorMessage,
885+
libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
886+
conn->sslmaxprotocolversion);
887+
return -1;
888+
}
889+
890+
if (!SSL_CTX_set_max_proto_version(SSL_context, ssl_max_ver))
891+
{
892+
char *err = SSLerrmessage(ERR_get_error());
893+
894+
printfPQExpBuffer(&conn->errorMessage,
895+
libpq_gettext("could not set maximum version of SSL protocol: %s\n"),
896+
err);
897+
return -1;
898+
}
899+
}
900+
846901
/*
847902
* Disable OpenSSL's moving-write-buffer sanity check, because it causes
848903
* unnecessary failures in nonblocking send cases.
@@ -1659,3 +1714,37 @@ PQssl_passwd_cb(char *buf, int size, int rwflag, void *userdata)
16591714
else
16601715
return PQdefaultSSLKeyPassHook(buf, size, conn);
16611716
}
1717+
1718+
/*
1719+
* Convert TLS protocol version string to OpenSSL values
1720+
*
1721+
* If a version is passed that is not supported by the current OpenSSL version,
1722+
* then we return -1. If a non-negative value is returned, subsequent code can
1723+
* assume it is working with a supported version.
1724+
*
1725+
* Note: this is rather similar to the backend routine in be-secure-openssl.c,
1726+
* so make sure to update both routines if changing this one.
1727+
*/
1728+
static int
1729+
ssl_protocol_version_to_openssl(const char *protocol)
1730+
{
1731+
if (pg_strcasecmp("TLSv1", protocol) == 0)
1732+
return TLS1_VERSION;
1733+
1734+
#ifdef TLS1_1_VERSION
1735+
if (pg_strcasecmp("TLSv1.1", protocol) == 0)
1736+
return TLS1_1_VERSION;
1737+
#endif
1738+
1739+
#ifdef TLS1_2_VERSION
1740+
if (pg_strcasecmp("TLSv1.2", protocol) == 0)
1741+
return TLS1_2_VERSION;
1742+
#endif
1743+
1744+
#ifdef TLS1_3_VERSION
1745+
if (pg_strcasecmp("TLSv1.3", protocol) == 0)
1746+
return TLS1_3_VERSION;
1747+
#endif
1748+
1749+
return -1;
1750+
}

src/interfaces/libpq/libpq-int.h

+2
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,8 @@ struct pg_conn
367367
char *krbsrvname; /* Kerberos service name */
368368
char *gsslib; /* What GSS library to use ("gssapi" or
369369
* "sspi") */
370+
char *sslminprotocolversion; /* minimum TLS protocol version */
371+
char *sslmaxprotocolversion; /* maximum TLS protocol version */
370372

371373
/* Type of connection to make. Possible values: any, read-write. */
372374
char *target_session_attrs;

src/test/ssl/t/001_ssltests.pl

+22-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
if ($ENV{with_openssl} eq 'yes')
1515
{
16-
plan tests => 86;
16+
plan tests => 93;
1717
}
1818
else
1919
{
@@ -356,6 +356,27 @@
356356
^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,_null_,_null_,_null_\r?$}mx,
357357
'pg_stat_ssl view without client certificate');
358358

359+
# Test min/max SSL protocol versions.
360+
test_connect_ok(
361+
$common_connstr,
362+
"sslrootcert=ssl/root+server_ca.crt sslmode=require sslminprotocolversion=TLSv1.2 sslmaxprotocolversion=TLSv1.2",
363+
"connection success with correct range of TLS protocol versions");
364+
test_connect_fails(
365+
$common_connstr,
366+
"sslrootcert=ssl/root+server_ca.crt sslmode=require sslminprotocolversion=TLSv1.2 sslmaxprotocolversion=TLSv1.1",
367+
qr/invalid SSL protocol version range/,
368+
"connection failure with incorrect range of TLS protocol versions");
369+
test_connect_fails(
370+
$common_connstr,
371+
"sslrootcert=ssl/root+server_ca.crt sslmode=require sslminprotocolversion=incorrect_tls",
372+
qr/invalid sslminprotocolversion value/,
373+
"connection failure with an incorrect SSL protocol minimum bound");
374+
test_connect_fails(
375+
$common_connstr,
376+
"sslrootcert=ssl/root+server_ca.crt sslmode=require sslmaxprotocolversion=incorrect_tls",
377+
qr/invalid sslmaxprotocolversion value/,
378+
"connection failure with an incorrect SSL protocol maximum bound");
379+
359380
### Server-side tests.
360381
###
361382
### Test certificate authorization.

0 commit comments

Comments
 (0)