The attached patches add an ssl_protocols configuration option which control which versions of SSL or TLS the server will use. The syntax is similar to Apache's SSLProtocols directive, except that the list is colon-separated instead of whitespace-separated, although that is easy to change if it proves unpopular.
Summary of the patch: - In src/backend/libpq/be-secure.c: - Add an SSLProtocols variable for the option. - Add a function, parse_SSL_protocols(), that parses an ssl_protocols string and returns a bitmask suitable for SSL_CTX_set_options(). - Change initialize_SSL() to call parse_SSL_protocols() and pass the result to SSL_CTX_set_options(). - In src/backend/utils/misc/guc.c: - Add an extern declaration for SSLProtocols. - Add an entry in the ConfigureNamesString array for the ssl_protocols option. - In src/backend/utils/misc/postgresql.conf.sample: - Add a sample ssl_protocols line. - In doc/src/sgml/config.sgml: - Document the ssl_protocols option. The file names are slightly different in 9.5, since be-secure.c was split in two and the declaration was moved into libpq.h. The default is "ALL:-SSLv2" in 9.0-9.3 and "ALL:-SSL" in 9.4 and up. This corresponds to the current hardcoded values, so the default behavior is unchanged, but the admin now has the option to select a different settings, e.g. if a serious vulnerability is found in TLS 1.0.
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 6ee17d8..7233a73 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -1027,6 +1027,34 @@ include_dir 'conf.d' </listitem> </varlistentry> + <varlistentry id="guc-ssl-protocols" xreflabel="ssl_protocols"> + <term><varname>ssl_protocols</varname> (<type>string</type>)</term> + <indexterm> + <primary><varname>ssl_protocols</> configuration parameter</primary> + </indexterm> + <listitem> + <para> + Specifies a colon-separated list of <acronym>SSL</> protocols that are + allowed to be used on secure connections. Each entry in the list can + be prefixed by a <literal>+</> (add to the current list) + or <literal>-</> (remove from the current list). If no prefix is used, + the list is replaced with the specified protocol. + </para> + <para> + The full list of supported protocols can be found in the + the <application>openssl</> manual page. In addition to the names of + individual protocols, the following keywords can be + used: <literal>ALL</> (all protocols supported by the underlying + crypto library), <literal>SSL</> (all supported versions + of <acronym>SSL</>) and <literal>TLS</> (all supported versions + of <acronym>TLS</>). + </para> + <para> + The default is <literal>ALL:-SSL</literal>. + </para> + </listitem> + </varlistentry> + <varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers"> <term><varname>ssl_ciphers</varname> (<type>string</type>) <indexterm> diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c index b05364c..f440b77 100644 --- a/src/backend/libpq/be-secure-openssl.c +++ b/src/backend/libpq/be-secure-openssl.c @@ -87,6 +87,7 @@ static int verify_cb(int, X509_STORE_CTX *); static void info_cb(const SSL *ssl, int type, int args); static void initialize_ecdh(void); static const char *SSLerrmessage(void); +static long parse_SSL_protocols(const char *str); /* are we in the middle of a renegotiation? */ static bool in_ssl_renegotiation = false; @@ -245,15 +246,16 @@ be_tls_init(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)"); @@ -1053,3 +1055,106 @@ SSLerrmessage(void) snprintf(errbuf, sizeof(errbuf), _("SSL error code %lu"), errcode); 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); +} diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c index 41ec1ad..a5a5f3f 100644 --- a/src/backend/libpq/be-secure.c +++ b/src/backend/libpq/be-secure.c @@ -52,7 +52,8 @@ int ssl_renegotiation_limit; 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. */ diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index dca533a..3a34be1 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -3239,6 +3239,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 dac6776..6b21fc5 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) diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h index 5da9d8d..15d0c3f 100644 --- a/src/include/libpq/libpq.h +++ b/src/include/libpq/libpq.h @@ -88,6 +88,7 @@ extern ssize_t secure_raw_write(Port *port, const void *ptr, size_t len); extern bool ssl_loaded_verify_locations; /* GUCs */ +extern char *SSLProtocols; extern char *SSLCipherSuites; extern char *SSLECDHCurve; extern bool SSLPreferServerCiphers;
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' </listitem> </varlistentry> + <varlistentry id="guc-ssl-protocols" xreflabel="ssl_protocols"> + <term><varname>ssl_protocols</varname> (<type>string</type>)</term> + <indexterm> + <primary><varname>ssl_protocols</> configuration parameter</primary> + </indexterm> + <listitem> + <para> + Specifies a colon-separated list of <acronym>SSL</> protocols that are + allowed to be used on secure connections. Each entry in the list can + be prefixed by a <literal>+</> (add to the current list) + or <literal>-</> (remove from the current list). If no prefix is used, + the list is replaced with the specified protocol. + </para> + <para> + The full list of supported protocols can be found in the + the <application>openssl</> manual page. In addition to the names of + individual protocols, the following keywords can be + used: <literal>ALL</> (all protocols supported by the underlying + crypto library), <literal>SSL</> (all supported versions + of <acronym>SSL</>) and <literal>TLS</> (all supported versions + of <acronym>TLS</>). + </para> + <para> + The default is <literal>ALL:-SSL</literal>. + </para> + </listitem> + </varlistentry> + <varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers"> <term><varname>ssl_ciphers</varname> (<type>string</type>) <indexterm> 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)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index c9276a3..ab2941b 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -873,6 +873,34 @@ include 'filename' </listitem> </varlistentry> + <varlistentry id="guc-ssl-protocols" xreflabel="ssl_protocols"> + <term><varname>ssl_protocols</varname> (<type>string</type>)</term> + <indexterm> + <primary><varname>ssl_protocols</> configuration parameter</primary> + </indexterm> + <listitem> + <para> + Specifies a colon-separated list of <acronym>SSL</> protocols that are + allowed to be used on secure connections. Each entry in the list can + be prefixed by a <literal>+</> (add to the current list) + or <literal>-</> (remove from the current list). If no prefix is used, + the list is replaced with the specified protocol. + </para> + <para> + The full list of supported protocols can be found in the + the <application>openssl</> manual page. In addition to the names of + individual protocols, the following keywords can be + used: <literal>ALL</> (all protocols supported by the underlying + crypto library), <literal>SSL</> (all supported versions + of <acronym>SSL</>) and <literal>TLS</> (all supported versions + of <acronym>TLS</>). + </para> + <para> + The default is <literal>ALL:-SSLv2</literal>. + </para> + </listitem> + </varlistentry> + <varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers"> <term><varname>ssl_ciphers</varname> (<type>string</type>)</term> <indexterm> diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c index d1beda8..c5d1558 100644 --- a/src/backend/libpq/be-secure.c +++ b/src/backend/libpq/be-secure.c @@ -87,6 +87,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; @@ -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) @@ -1055,4 +1060,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 2b6527f..42d4ad1 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -125,6 +125,7 @@ extern char *temp_tablespaces; extern bool ignore_checksum_failure; extern bool synchronize_seqscans; extern int ssl_renegotiation_limit; +extern char *SSLProtocols; extern char *SSLCipherSuites; #ifdef TRACE_SORT @@ -3117,6 +3118,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 12f1cba..64d38a9 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:-SSLv2' # allowed SSL protocols #ssl_ciphers = 'DEFAULT:!LOW:!EXP:!MD5:@STRENGTH' # allowed SSL ciphers # (change requires restart) #ssl_renegotiation_limit = 512MB # amount of data between renegotiations
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 51d7da9..628f523 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -789,6 +789,34 @@ SET ENABLE_SEQSCAN TO OFF; </listitem> </varlistentry> + <varlistentry id="guc-ssl-protocols" xreflabel="ssl_protocols"> + <term><varname>ssl_protocols</varname> (<type>string</type>)</term> + <indexterm> + <primary><varname>ssl_protocols</> configuration parameter</primary> + </indexterm> + <listitem> + <para> + Specifies a colon-separated list of <acronym>SSL</> protocols that are + allowed to be used on secure connections. Each entry in the list can + be prefixed by a <literal>+</> (add to the current list) + or <literal>-</> (remove from the current list). If no prefix is used, + the list is replaced with the specified protocol. + </para> + <para> + The full list of supported protocols can be found in the + the <application>openssl</> manual page. In addition to the names of + individual protocols, the following keywords can be + used: <literal>ALL</> (all protocols supported by the underlying + crypto library), <literal>SSL</> (all supported versions + of <acronym>SSL</>) and <literal>TLS</> (all supported versions + of <acronym>TLS</>). + </para> + <para> + The default is <literal>ALL:-SSLv2</literal>. + </para> + </listitem> + </varlistentry> + <varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers"> <term><varname>ssl_ciphers</varname> (<type>string</type>)</term> <indexterm> diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c index bccea54..9b8871b 100644 --- a/src/backend/libpq/be-secure.c +++ b/src/backend/libpq/be-secure.c @@ -87,6 +87,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; @@ -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) @@ -1055,4 +1060,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 e5ee0f8..cc8edbc 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -132,6 +132,7 @@ extern char *default_tablespace; extern char *temp_tablespaces; extern bool synchronize_seqscans; extern int ssl_renegotiation_limit; +extern char *SSLProtocols; extern char *SSLCipherSuites; #ifdef TRACE_SORT @@ -3043,6 +3044,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 6fe6924..8cca6b9 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
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; </listitem> </varlistentry> + <varlistentry id="guc-ssl-protocols" xreflabel="ssl_protocols"> + <term><varname>ssl_protocols</varname> (<type>string</type>)</term> + <indexterm> + <primary><varname>ssl_protocols</> configuration parameter</primary> + </indexterm> + <listitem> + <para> + Specifies a colon-separated list of <acronym>SSL</> protocols that are + allowed to be used on secure connections. Each entry in the list can + be prefixed by a <literal>+</> (add to the current list) + or <literal>-</> (remove from the current list). If no prefix is used, + the list is replaced with the specified protocol. + </para> + <para> + The full list of supported protocols can be found in the + the <application>openssl</> manual page. In addition to the names of + individual protocols, the following keywords can be + used: <literal>ALL</> (all protocols supported by the underlying + crypto library), <literal>SSL</> (all supported versions + of <acronym>SSL</>) and <literal>TLS</> (all supported versions + of <acronym>TLS</>). + </para> + <para> + The default is <literal>ALL:-SSLv2</literal>. + </para> + </listitem> + </varlistentry> + <varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers"> <term><varname>ssl_ciphers</varname> (<type>string</type>)</term> <indexterm> 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
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 2f37a29..71efa6f 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -685,6 +685,34 @@ SET ENABLE_SEQSCAN TO OFF; </listitem> </varlistentry> + <varlistentry id="guc-ssl-protocols" xreflabel="ssl_protocols"> + <term><varname>ssl_protocols</varname> (<type>string</type>)</term> + <indexterm> + <primary><varname>ssl_protocols</> configuration parameter</primary> + </indexterm> + <listitem> + <para> + Specifies a colon-separated list of <acronym>SSL</> protocols that are + allowed to be used on secure connections. Each entry in the list can + be prefixed by a <literal>+</> (add to the current list) + or <literal>-</> (remove from the current list). If no prefix is used, + the list is replaced with the specified protocol. + </para> + <para> + The full list of supported protocols can be found in the + the <application>openssl</> manual page. In addition to the names of + individual protocols, the following keywords can be + used: <literal>ALL</> (all protocols supported by the underlying + crypto library), <literal>SSL</> (all supported versions + of <acronym>SSL</>) and <literal>TLS</> (all supported versions + of <acronym>TLS</>). + </para> + <para> + The default is <literal>ALL:-SSLv2</literal>. + </para> + </listitem> + </varlistentry> + <varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers"> <term><varname>ssl_ciphers</varname> (<type>string</type>)</term> <indexterm> diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c index 28e3102..e10bcdd 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 /* @@ -105,7 +106,8 @@ int ssl_renegotiation_limit; static SSL_CTX *SSL_context = NULL; static bool ssl_loaded_verify_locations = false; -/* GUC variable controlling SSL cipher list */ +/* GUC variables controlling SSL protocol and cipher list */ +char *SSLProtocols = NULL; char *SSLCipherSuites = NULL; #endif @@ -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 718de95..563267e 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -130,6 +130,7 @@ extern bool optimize_bounded_sort; #endif #ifdef USE_SSL +extern char *SSLProtocols; extern char *SSLCipherSuites; #endif @@ -2638,6 +2639,21 @@ static struct config_string ConfigureNamesString[] = #ifdef USE_SSL { + {"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 155af1c..b2e1023 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
DES -- Dag-Erling Smørgrav - d...@des.no
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers