diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 179c60e..4ae3ae3 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -689,6 +689,34 @@ SET ENABLE_SEQSCAN TO OFF; + + 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:-SSLv2. + + + + ssl_ciphers (string) diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c index 6e09496..1d3c09b 100644 --- a/src/backend/libpq/be-secure.c +++ b/src/backend/libpq/be-secure.c @@ -92,6 +92,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 /* @@ -106,7 +107,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; /* ------------------------------------------------------------ */ @@ -793,9 +795,12 @@ initialize_SSL(void) SSLerrmessage()))); } - /* set up ephemeral DH keys, and disallow SSL v2 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_CTX_set_options(SSL_context, SSL_OP_SINGLE_DH_USE); + + /* 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) @@ -1074,4 +1079,107 @@ 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", but it + * is 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 */ + if (result & SSL_PROTO_SSLv2) { + elog(WARNING, "removing SSLv2 from SSL protocol list"); + result &= ~SSL_PROTO_SSLv2; + } + /* 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 a66a7d9..c2dc017 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -131,6 +131,7 @@ extern char *temp_tablespaces; extern bool synchronize_seqscans; extern bool fullPageWrites; extern int ssl_renegotiation_limit; +extern char *SSLProtocols; extern char *SSLCipherSuites; #ifdef TRACE_SORT @@ -2987,6 +2988,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:-SSLv2", +#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 82c8ae4..8acc5a5 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -78,6 +78,7 @@ #authentication_timeout = 1min # 1s-600s #ssl = off # (change requires restart) +#ssl_protocols = 'ALL:-SSLv2' # allowed SSL protocols #ssl_ciphers = 'ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH' # allowed SSL ciphers # (change requires restart) #ssl_renegotiation_limit = 512MB # amount of data between renegotiations