diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 2fb9217..50bd51f 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -1027,6 +1027,34 @@ include_dir 'conf.d' + + ssl_protocols (string) + + ssl_protocols configuration parameter + + + + Specifies a colon-separated list of SSL protocols that are + allowed to be used on secure connections. Each entry in the list can + be prefixed by a + (add to the current list) + or - (remove from the current list). If no prefix is used, + the list is replaced with the specified protocol. + + + The full list of supported protocols can be found in the + the openssl manual page. In addition to the names of + individual protocols, the following keywords can be + used: ALL (all protocols supported by the underlying + crypto library), SSL (all supported versions + of SSL) and TLS (all supported versions + of TLS). + + + The default is ALL:-SSL. + + + + ssl_ciphers (string) diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c index 89c30d0..acebdc2 100644 --- a/src/backend/libpq/be-secure.c +++ b/src/backend/libpq/be-secure.c @@ -90,6 +90,7 @@ static void initialize_SSL(void); static int open_server_SSL(Port *); static void close_SSL(Port *); static const char *SSLerrmessage(void); +static long parse_SSL_protocols(const char *str); #endif char *ssl_cert_file; @@ -112,7 +113,8 @@ static SSL_CTX *SSL_context = NULL; static bool ssl_loaded_verify_locations = false; #endif -/* GUC variable controlling SSL cipher list */ +/* GUC variables controlling SSL protocol and cipher list */ +char *SSLProtocols = NULL; char *SSLCipherSuites = NULL; /* GUC variable for default ECHD curve. */ @@ -887,15 +889,16 @@ initialize_SSL(void) SSLerrmessage()))); } - /* set up ephemeral DH keys, and disallow SSL v2/v3 while at it */ + /* set up ephemeral DH keys */ SSL_CTX_set_tmp_dh_callback(SSL_context, tmp_dh_cb); - SSL_CTX_set_options(SSL_context, - SSL_OP_SINGLE_DH_USE | - SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); + SSL_CTX_set_options(SSL_context, SSL_OP_SINGLE_DH_USE); /* set up ephemeral ECDH keys */ initialize_ecdh(); + /* set up the allowed protocol list */ + SSL_CTX_set_options(SSL_context, parse_SSL_protocols(SSLProtocols)); + /* set up the allowed cipher list */ if (SSL_CTX_set_cipher_list(SSL_context, SSLCipherSuites) != 1) elog(FATAL, "could not set the cipher list (no valid ciphers available)"); @@ -1158,4 +1161,106 @@ SSLerrmessage(void) return errbuf; } +/* + * Parse the SSL allowed protocol list + * + * The logic here is inverted. OpenSSL does not take a list of + * protocols to use, but a list of protocols to avoid. We use the + * same bits with the opposite meaning, then invert the result. + */ + +#define SSL_PROTO_SSLv2 SSL_OP_NO_SSLv2 +#define SSL_PROTO_SSLv3 SSL_OP_NO_SSLv3 +#define SSL_PROTO_SSL (SSL_PROTO_SSLv2 | SSL_PROTO_SSLv3) +#define SSL_PROTO_TLSv1 SSL_OP_NO_TLSv1 +#ifdef SSL_OP_NO_TLSv1_1 +#define SSL_PROTO_TLSv1_1 SSL_OP_NO_TLSv1_1 +#else +#define SSL_PROTO_TLSv1_1 0 +#endif +#ifdef SSL_OP_NO_TLSv1_2 +#define SSL_PROTO_TLSv1_2 SSL_OP_NO_TLSv1_2 +#else +#define SSL_PROTO_TLSv1_2 0 +#endif +#define SSL_PROTO_TLS (SSL_PROTO_TLSv1 | SSL_PROTO_TLSv1_1 | SSL_PROTO_TLSv1_2) +#define SSL_PROTO_ALL (SSL_PROTO_SSL | SSL_PROTO_TLS) +#define SSL_PROTO_NONE 0 + +#define str_is_token(str, tok, len) \ + (len == sizeof(tok) - 1 && pg_strncasecmp(str, tok, len) == 0) + +static long +parse_SSL_protocols(const char *str) +{ + long current, result; + const char *p, *q; + int action; + + /* + * Iterate over the colon-separated list of protocols. If a protocol is + * preceded by a +, it is added to the list. If it is preceded by a -, it + * is removed from the list. If it is not preceded by anything, the list + * is set to exactly that protocol. "ALL" can be used to indicate all + * protocols, "NONE" to indicate no protocols "SSL" to indicate all SSL + * protocols and "TLS" to indicate all TLS protocols. The parser accepts + * "SSLv2", "SSLv3" and "SSL", but they are removed after parsing. + */ + result = SSL_PROTO_NONE; + for (p = q = str; *q; p = q + 1) { + for (q = p; *q && *q != ':'; ++q) + /* nothing */ ; + if (*p == '-' || *p == '+') + action = *p++; + else + action = '='; + if (str_is_token(p, "ALL", q - p)) + current = SSL_PROTO_ALL; + else if (str_is_token(p, "NONE", q - p)) + current = SSL_PROTO_NONE; + else if (str_is_token(p, SSL_TXT_SSLV2, q - p)) + current = SSL_PROTO_SSLv2; + else if (str_is_token(p, SSL_TXT_SSLV3, q - p)) + current = SSL_PROTO_SSLv3; + else if (str_is_token(p, "SSL", q - p)) + current = SSL_PROTO_SSL; + else if (str_is_token(p, SSL_TXT_TLSV1, q - p)) + current = SSL_PROTO_TLSv1; +#ifdef SSL_OP_NO_TLSv1_1 + else if (str_is_token(p, SSL_TXT_TLSV1_1, q - p)) + current = SSL_PROTO_TLSv1_1; +#endif +#ifdef SSL_OP_NO_TLSv1_2 + else if (str_is_token(p, SSL_TXT_TLSV1_2, q - p)) + current = SSL_PROTO_TLSv1_2; +#endif + else if (str_is_token(p, "TLS", q - p)) + current = SSL_PROTO_TLS; + else + elog(FATAL, "invalid SSL protocol list"); + switch (action) { + case '+': + result |= current; + break; + case '-': + result &= ~current; + break; + default: + result = current; + break; + } + } + /* forcibly disallow SSLv2 and SSLv3 */ + if (result & SSL_PROTO_SSL) { + elog(WARNING, "removing SSLv2 and SSLv3 from SSL protocol list"); + result &= ~SSL_PROTO_SSL; + } + /* check the result */ + if (result == 0) + elog(FATAL, "could not set the protocol list (no valid protocols available)"); + elog(DEBUG2, "enabling SSL protocols: %lx", result); + /* return the inverse */ + return (SSL_PROTO_ALL & ~result); +} + #endif /* USE_SSL */ diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index ea58cf6..3d1b668 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -125,6 +125,7 @@ extern char *default_tablespace; extern char *temp_tablespaces; extern bool ignore_checksum_failure; extern bool synchronize_seqscans; +extern char *SSLProtocols; extern char *SSLCipherSuites; extern char *SSLECDHCurve; extern bool SSLPreferServerCiphers; @@ -3215,6 +3216,21 @@ static struct config_string ConfigureNamesString[] = }, { + {"ssl_protocols", PGC_POSTMASTER, CONN_AUTH_SECURITY, + gettext_noop("Sets the list of allowed SSL protocols."), + NULL, + GUC_SUPERUSER_ONLY + }, + &SSLProtocols, +#ifdef USE_SSL + "ALL:-SSL", +#else + "none", +#endif + NULL, NULL, NULL + }, + + { {"ssl_ciphers", PGC_POSTMASTER, CONN_AUTH_SECURITY, gettext_noop("Sets the list of allowed SSL ciphers."), NULL, diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index 8d5bb19..d161113 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -79,6 +79,7 @@ #authentication_timeout = 1min # 1s-600s #ssl = off # (change requires restart) +#ssl_protocols = 'ALL:-SSL' # allowed SSL protocols #ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL' # allowed SSL ciphers # (change requires restart) #ssl_prefer_server_ciphers = on # (change requires restart)