From 6c365125c33ecf7bac7b4fc3f795380c8a0ea782 Mon Sep 17 00:00:00 2001 From: Daniel Gustafsson 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 + + sslminprotocolversion + + + This parameter specifies the minimum SSL/TLS protocol version to allow + for the connection. Valid values are TLSv1, + TLSv1.1, TLSv1.2 and + TLSv1.3. The supported protocols depend on the + version of OpenSSL used, older versions + doesn't support the modern protocol versions. + + + + + + sslmaxprotocolversion + + + This parameter specifies the maximum SSL/TLS protocol version to allow + for the connection. The supported values are the same as for + sslminprotocolversion. 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. + + + + krbsrvname @@ -7021,6 +7048,26 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough) + + + + PGSSLMINPROTOCOLVERSION + + PGSSLMINPROTOCOLVERSION behaves the same as the connection parameter. + + + + + + + PGSSLMAXPROTOCOLVERSION + + PGSSLMAXPROTOCOLVERSION behaves the same as the connection parameter. + + + @@ -7674,6 +7721,24 @@ ldap://ldap.acme.com/cn=dbserver,cn=hosts?pgconnectinfo?base?(objectclass=*) + + Client Protocol Usage + + + When connecting using SSL, the client and server negotiate which protocol + to use for the connection. PostgreSQL supports + TLSv1, TLSv1.1, TLSv1.2 + and TLSv1.3, but the protocols available depends on the + version of OpenSSL which the client is using. + The minimum requested version can be specified with sslminprotocolversion, + which will ensure that the connection use that version, or higher, or fails. + The maximum requested version can be specified with sslmaxprotocolversion, + but this is mainly only useful for testing, or in case a component doesn't + work with a newer protocol. + + + + SSL Client File Usage 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)