From e73e67c719593c1c16139cc6c516d8379f22f182 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Tue, 20 Nov 2018 21:49:01 +0100 Subject: Add settings to control SSL/TLS protocol version For example: ssl_min_protocol_version = 'TLSv1.1' ssl_max_protocol_version = 'TLSv1.2' Reviewed-by: Steve Singer Discussion: https://www.postgresql.org/message-id/flat/1822da87-b862-041a-9fc2-d0310c3da173@2ndquadrant.com --- doc/src/sgml/config.sgml | 44 +++++++++ src/backend/libpq/be-secure-openssl.c | 123 +++++++++++++++++++++++++- src/backend/libpq/be-secure.c | 3 + src/backend/utils/misc/guc.c | 33 +++++++ src/backend/utils/misc/postgresql.conf.sample | 2 + src/include/libpq/libpq.h | 11 +++ 6 files changed, 214 insertions(+), 2 deletions(-) diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index c4effa034c..5d76862f46 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -1291,6 +1291,50 @@ include_dir 'conf.d' + + ssl_min_protocol_version (enum) + + ssl_min_protocol_version configuration parameter + + + + + Sets the minimum SSL/TLS protocol version to use. Valid values are + currently: TLSv1, TLSv1.1, + TLSv1.2, TLSv1.3. Older + versions of the OpenSSL library do not + support all values; an error will be raised if an unsupported setting + is chosen. Protocol versions before TLS 1.0, namely SSL version 2 and + 3, are always disabled. + + + + The default is TLSv1, mainly to support older + versions of the OpenSSL library. You might + want to set this to a higher value if all software components can + support the newer protocol versions. + + + + + + ssl_max_protocol_version (enum) + + ssl_max_protocol_version configuration parameter + + + + + Sets the maximum SSL/TLS protocol version to use. Valid values are as + for , with addition of + an empty string, which allows any protocol version. The default is to + allow any version. Setting the maximum protocol version is mainly + useful for testing or if some component has issues working with a + newer protocol. + + + + ssl_dh_params_file (string) diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c index 6a576572bb..b2b0cccdae 100644 --- a/src/backend/libpq/be-secure-openssl.c +++ b/src/backend/libpq/be-secure-openssl.c @@ -67,6 +67,12 @@ static bool SSL_initialized = false; static bool dummy_ssl_passwd_cb_called = false; static bool ssl_is_server_start; +static int ssl_protocol_version_to_openssl(int v, const char *guc_name); +#if (OPENSSL_VERSION_NUMBER < 0x10100000L) +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 */ @@ -183,8 +189,14 @@ be_tls_init(bool isServerStart) goto error; } - /* disallow SSL v2/v3 */ - SSL_CTX_set_options(context, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); + if (ssl_min_protocol_version) + SSL_CTX_set_min_proto_version(context, + ssl_protocol_version_to_openssl(ssl_min_protocol_version, + "ssl_min_protocol_version")); + if (ssl_max_protocol_version) + SSL_CTX_set_max_proto_version(context, + ssl_protocol_version_to_openssl(ssl_max_protocol_version, + "ssl_max_protocol_version")); /* disallow SSL session tickets */ #ifdef SSL_OP_NO_TICKET /* added in OpenSSL 0.9.8f */ @@ -1209,3 +1221,110 @@ X509_NAME_to_cstring(X509_NAME *name) return result; } + +/* + * Convert TLS protocol version GUC enum to OpenSSL values + * + * This is a straightforward one-to-one mapping, but doing it this way makes + * guc.c independent of OpenSSL availability and version. + * + * If a version is passed that is not supported by the current OpenSSL + * version, then we throw an error, so that subsequent code can assume it's + * working with a supported version. + */ +static int +ssl_protocol_version_to_openssl(int v, const char *guc_name) +{ + switch (v) + { + case PG_TLS_ANY: + return 0; + case PG_TLS1_VERSION: + return TLS1_VERSION; + case PG_TLS1_1_VERSION: +#ifdef TLS1_1_VERSION + return TLS1_1_VERSION; +#else + goto error; +#endif + case PG_TLS1_2_VERSION: +#ifdef TLS1_2_VERSION + return TLS1_2_VERSION; +#else + goto error; +#endif + case PG_TLS1_3_VERSION: +#ifdef TLS1_3_VERSION + return TLS1_3_VERSION; +#else + goto error; +#endif + } + +error: + pg_attribute_unused(); + ereport(ERROR, + (errmsg("%s setting %s not supported by this build", + guc_name, + GetConfigOption(guc_name, false, false)))); + return -1; +} + +/* + * Replacements for APIs present in newer versions of OpenSSL + */ +#if (OPENSSL_VERSION_NUMBER < 0x10100000L) + +/* + * 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; +#ifdef TLS1_1_VERSION + if (version > TLS1_1_VERSION) + ssl_options |= SSL_OP_NO_TLSv1_1; +#endif +#ifdef TLS1_2_VERSION + if (version > TLS1_2_VERSION) + ssl_options |= SSL_OP_NO_TLSv1_2; +#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); + +#ifdef TLS1_1_VERSION + if (version < TLS1_1_VERSION) + ssl_options |= SSL_OP_NO_TLSv1_1; +#endif +#ifdef TLS1_2_VERSION + if (version < TLS1_2_VERSION) + ssl_options |= SSL_OP_NO_TLSv1_2; +#endif + + SSL_CTX_set_options(ctx, ssl_options); + + return 1; /* success */ +} + +#endif /* OPENSSL_VERSION_NUMBER */ diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c index 4eb21fe89d..7cfafb5908 100644 --- a/src/backend/libpq/be-secure.c +++ b/src/backend/libpq/be-secure.c @@ -60,6 +60,9 @@ char *SSLECDHCurve; /* GUC variable: if false, prefer client ciphers */ bool SSLPreferServerCiphers; +int ssl_min_protocol_version; +int ssl_max_protocol_version; + /* ------------------------------------------------------------ */ /* Procedures common to all secure sessions */ /* ------------------------------------------------------------ */ diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 7e9e8c642b..19c678f596 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -428,6 +428,15 @@ static const struct config_enum_entry password_encryption_options[] = { {NULL, 0, false} }; +const struct config_enum_entry ssl_protocol_versions_info[] = { + {"", PG_TLS_ANY, false}, + {"TLSv1", PG_TLS1_VERSION, false}, + {"TLSv1.1", PG_TLS1_1_VERSION, false}, + {"TLSv1.2", PG_TLS1_2_VERSION, false}, + {"TLSv1.3", PG_TLS1_3_VERSION, false}, + {NULL, 0, false} +}; + /* * Options for enum values stored in other modules */ @@ -4193,6 +4202,30 @@ static struct config_enum ConfigureNamesEnum[] = NULL, NULL, NULL }, + { + {"ssl_min_protocol_version", PGC_SIGHUP, CONN_AUTH_SSL, + gettext_noop("Sets the minimum SSL/TLS protocol version to use."), + NULL, + GUC_SUPERUSER_ONLY + }, + &ssl_min_protocol_version, + PG_TLS1_VERSION, + ssl_protocol_versions_info + 1 /* don't allow PG_TLS_ANY */, + NULL, NULL, NULL + }, + + { + {"ssl_max_protocol_version", PGC_SIGHUP, CONN_AUTH_SSL, + gettext_noop("Sets the maximum SSL/TLS protocol version to use."), + NULL, + GUC_SUPERUSER_ONLY + }, + &ssl_max_protocol_version, + PG_TLS_ANY, + ssl_protocol_versions_info, + NULL, NULL, NULL + }, + /* End-of-list marker */ { {NULL, 0, 0, NULL, NULL}, NULL, 0, NULL, NULL, NULL, NULL diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index ab063dae41..26d5c4c967 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -103,6 +103,8 @@ #ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL' # allowed SSL ciphers #ssl_prefer_server_ciphers = on #ssl_ecdh_curve = 'prime256v1' +#ssl_min_protocol_version = 'TLSv1' +#ssl_max_protocol_version = '' #ssl_dh_params_file = '' #ssl_passphrase_command = '' #ssl_passphrase_command_supports_reload = off diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h index c7762f68a6..e81752446e 100644 --- a/src/include/libpq/libpq.h +++ b/src/include/libpq/libpq.h @@ -102,6 +102,17 @@ extern WaitEventSet *FeBeWaitSet; extern char *SSLCipherSuites; extern char *SSLECDHCurve; extern bool SSLPreferServerCiphers; +extern int ssl_min_protocol_version; +extern int ssl_max_protocol_version; + +enum ssl_protocol_versions +{ + PG_TLS_ANY = 0, + PG_TLS1_VERSION, + PG_TLS1_1_VERSION, + PG_TLS1_2_VERSION, + PG_TLS1_3_VERSION, +}; /* * prototypes for functions in be-secure-common.c -- cgit v1.2.1