Michael Paquier <michael.paqu...@gmail.com> writes: >>> >>> Perhaps ssloptions.[ch], unless you plan to add non-option-related code >>> there later? >> >> I don't think anything else than common options-related code fits in >> there, so ssloptions.c makes sense to me. >> >>> BTW, there is no Regent code in your openssl.c, so the copyright >>> statement is incorrect. >> >> Good catch, I just blindly copied that from some other file. > There have been arguments in favor and against this patch... But > marking it as returned with feedback because of a lack of activity in > the last couple of weeks. Feel free to update if you think that's not > correct, and please move this patch to commit fest 2014-12.
Attached is a new version that addresses the earlier feedback: renamed the added *.[ch] files and removed incorrect copyright line. I'm moving this to the current CF. -- Alex
>From 18388300f9ed34fa47c66b8a2da098aeb19a7f71 Mon Sep 17 00:00:00 2001 From: Alex Shulgin <a...@commandprompt.com> Date: Wed, 19 Nov 2014 19:49:29 +0300 Subject: [PATCH] DRAFT: ssl_protocols GUC --- doc/src/sgml/config.sgml | 29 ++++++ doc/src/sgml/libpq.sgml | 25 ++++++ src/backend/libpq/Makefile | 2 +- src/backend/libpq/be-secure-openssl.c | 29 ++++-- src/backend/libpq/be-secure.c | 3 +- src/backend/libpq/ssloptions.c | 123 ++++++++++++++++++++++++++ src/backend/utils/misc/guc.c | 15 ++++ src/backend/utils/misc/postgresql.conf.sample | 1 + src/include/libpq/libpq.h | 1 + src/include/libpq/ssloptions.h | 48 ++++++++++ src/interfaces/libpq/Makefile | 8 +- src/interfaces/libpq/fe-connect.c | 4 + src/interfaces/libpq/fe-secure-openssl.c | 18 +++- src/interfaces/libpq/libpq-int.h | 1 + 14 files changed, 297 insertions(+), 10 deletions(-) create mode 100644 src/backend/libpq/ssloptions.c create mode 100644 src/include/libpq/ssloptions.h diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml new file mode 100644 index 48ae3e4..699f0f9 *** a/doc/src/sgml/config.sgml --- b/doc/src/sgml/config.sgml *************** include_dir 'conf.d' *** 1055,1060 **** --- 1055,1089 ---- </listitem> </varlistentry> + <varlistentry id="guc-ssl-protocols" xreflabel="ssl_protocols"> + <term><varname>ssl_protocols</varname> (<type>string</type>) + <indexterm> + <primary><varname>ssl_protocols</> configuration parameter</primary> + </indexterm> + </term> + <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/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml new file mode 100644 index d829a4b..62ee0b4 *** a/doc/src/sgml/libpq.sgml --- b/doc/src/sgml/libpq.sgml *************** postgresql://%2Fvar%2Flib%2Fpostgresql/d *** 1230,1235 **** --- 1230,1250 ---- </listitem> </varlistentry> + <varlistentry id="libpq-connect-sslprotocols" xreflabel="sslprotocols"> + <term><literal>sslprotocols</literal></term> + <listitem> + <para> + Specifies a colon-separated list of <acronym>SSL</> protocols that are + allowed to be used on secure connections. + See <xref linkend="guc-ssl-protocols"> server configuration parameter + for format. + </para> + <para> + Defaults to <literal>ALL:-SSL</>. + </para> + </listitem> + </varlistentry> + <varlistentry id="libpq-connect-sslcompression" xreflabel="sslcompression"> <term><literal>sslcompression</literal></term> <listitem> *************** myEventProc(PGEventId evtId, void *evtIn *** 6693,6698 **** --- 6708,6723 ---- </para> </listitem> + <listitem> + <para> + <indexterm> + <primary><envar>PGSSLPROTOCOLS</envar></primary> + </indexterm> + <envar>PGSSLPROTOCOLS</envar> behaves the same as the <xref + linkend="libpq-connect-sslprotocols"> connection parameter. + </para> + </listitem> + <listitem> <para> <indexterm> diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile new file mode 100644 index 09410c4..62abd46 *** a/src/backend/libpq/Makefile --- b/src/backend/libpq/Makefile *************** OBJS = be-fsstubs.o be-secure.o auth.o c *** 18,24 **** pqformat.o pqmq.o pqsignal.o ifeq ($(with_openssl),yes) ! OBJS += be-secure-openssl.o endif include $(top_srcdir)/src/backend/common.mk --- 18,24 ---- pqformat.o pqmq.o pqsignal.o ifeq ($(with_openssl),yes) ! OBJS += be-secure-openssl.o ssloptions.o endif include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c new file mode 100644 index b05364c..ad7e0f6 *** a/src/backend/libpq/be-secure-openssl.c --- b/src/backend/libpq/be-secure-openssl.c *************** *** 71,76 **** --- 71,77 ---- #endif #include "libpq/libpq.h" + #include "libpq/ssloptions.h" #include "tcop/tcopprot.h" #include "utils/memutils.h" *************** KWbuHn491xNO25CQWMtem80uKw+pTnisBRF/454n *** 169,175 **** void be_tls_init(void) { ! struct stat buf; STACK_OF(X509_NAME) *root_cert_list = NULL; --- 170,178 ---- void be_tls_init(void) { ! struct stat buf; ! long protocol_options, ! disallow_ssl_options; STACK_OF(X509_NAME) *root_cert_list = NULL; *************** be_tls_init(void) *** 245,259 **** SSLerrmessage()))); } ! /* set up ephemeral DH keys, and disallow SSL v2/v3 while at it */ 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); /* set up ephemeral ECDH keys */ initialize_ecdh(); /* 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)"); --- 248,278 ---- SSLerrmessage()))); } ! /* 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); /* set up ephemeral ECDH keys */ initialize_ecdh(); + /* set up the allowed protocol list */ + if (!options_from_ssl_protocols_string(SSLProtocols, &protocol_options)) + ereport(FATAL, + (errmsg("invalid SSL protocol list: %s", SSLProtocols), + errhint("are you missing 'ALL:' before '-BADPROTO'?"))); + + /* forcibly disallow SSLv2 and SSLv3 */ + disallow_ssl_options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3; + + if ((protocol_options & disallow_ssl_options) != disallow_ssl_options) + { + elog(WARNING, "removing SSLv2 and SSLv3 from SSL protocol list"); + protocol_options |= disallow_ssl_options; + } + elog(DEBUG2, "disabling SSL protocols: %lx", protocol_options); + + SSL_CTX_set_options(SSL_context, protocol_options); + /* 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)"); diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c new file mode 100644 index 41ec1ad..a5a5f3f *** a/src/backend/libpq/be-secure.c --- b/src/backend/libpq/be-secure.c *************** int ssl_renegotiation_limit; *** 52,58 **** bool ssl_loaded_verify_locations = false; #endif ! /* GUC variable controlling SSL cipher list */ char *SSLCipherSuites = NULL; /* GUC variable for default ECHD curve. */ --- 52,59 ---- bool ssl_loaded_verify_locations = false; #endif ! /* 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/libpq/ssloptions.c b/src/backend/libpq/ssloptions.c new file mode 100644 index ...e161c1e *** a/src/backend/libpq/ssloptions.c --- b/src/backend/libpq/ssloptions.c *************** *** 0 **** --- 1,123 ---- + /*------------------------------------------------------------------------- + * + * ssloptions.c + * shared functions for setting OpenSSL options. + * + * + * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/libpq/ssloptions.c + * + * Contains functions that both backend and frontend can use to setup + * options for a secure connection. + * + *------------------------------------------------------------------------- + */ + + #include "postgres.h" + + #include <openssl/ssl.h> + #if SSLEAY_VERSION_NUMBER >= 0x0907000L + #include <openssl/conf.h> + #endif + + #include "libpq/libpq.h" + #include "libpq/ssloptions.h" + + /* + * 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) + + + bool + options_from_ssl_protocols_string(const char *str, long *options) + { + long result, current; + 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. + */ + 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 + return false; + switch (action) { + case '+': + result |= current; + break; + case '-': + result &= ~current; + break; + default: + result = current; + break; + } + } + if (result == SSL_PROTO_NONE) + return false; + + *options = (SSL_PROTO_ALL & ~result); + return true; + } diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c new file mode 100644 index b1bff7f..c54c55b *** a/src/backend/utils/misc/guc.c --- b/src/backend/utils/misc/guc.c *************** static struct config_string ConfigureNam *** 3245,3250 **** --- 3245,3265 ---- }, { + {"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 new file mode 100644 index b053659..bb44aa6 *** a/src/backend/utils/misc/postgresql.conf.sample --- b/src/backend/utils/misc/postgresql.conf.sample *************** *** 79,84 **** --- 79,85 ---- #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 new file mode 100644 index 2a61a9e..eb8dfee *** a/src/include/libpq/libpq.h --- b/src/include/libpq/libpq.h *************** extern ssize_t secure_raw_write(Port *po *** 108,113 **** --- 108,114 ---- extern bool ssl_loaded_verify_locations; /* GUCs */ + extern char *SSLProtocols; extern char *SSLCipherSuites; extern char *SSLECDHCurve; extern bool SSLPreferServerCiphers; diff --git a/src/include/libpq/ssloptions.h b/src/include/libpq/ssloptions.h new file mode 100644 index ...92e8561 *** a/src/include/libpq/ssloptions.h --- b/src/include/libpq/ssloptions.h *************** *** 0 **** --- 1,48 ---- + /*------------------------------------------------------------------------- + * + * ssloptions.h + * declarations of shared functions for setting OpenSSL options. + * + * + * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group + * + * src/include/libpq/ssloptions.h + * + *------------------------------------------------------------------------- + */ + #ifndef SSLOPTIONS_H + #define SSLOPTIONS_H + + /* + * Parse the list of SSL protocols in the colon-separated format and return a + * bitmask of options to be passed to SSL_CTX_set_options(). + * + * Recognized protocol names are SSLv2, SSLv3 and TLSv1. + * + * In case the OpenSSL version defines SSL_OP_NO_TLSv1_1, the string TLSv1.1 + * is also recognized (ditto for SSL_OP_NO_TLSv1_2 and TLSv1.2). + * + * Additionally, SSL means all supported SSL versions, the same goes for TLS. + * Furthermore, ALL means all supported protocols (and NONE, well you've + * guessed it.) + * + * A plus sign before the protocol name includes a protocol in the list, minus + * removes, otherwise replaces the whole list. + * + * Example: ALL:-SSL + * + * The above example includes all supported protocols except all SSL versions + * (SSLv2 and SSLv3). + * + * Since OpenSSL API only let you *exclude* protocols in the options, the + * resulting bitmask will have SSL_OP_NO_<protocol> flags set for which there + * were protocols listed with a minus sign. + * + * Returns true on success, false otherwise (i.e. unrecognized protocol + * found in the list). + * + * On success, stores the result of parsing in the options out-parameter. + */ + extern bool options_from_ssl_protocols_string(const char *str, long *options); + + #endif /* SSLOPTIONS_H */ diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile new file mode 100644 index 18d9b85..31acdc3 *** a/src/interfaces/libpq/Makefile --- b/src/interfaces/libpq/Makefile *************** OBJS += chklocale.o inet_net_ntop.o nobl *** 40,51 **** --- 40,55 ---- # libpgport C files that are needed if identified by configure OBJS += $(filter crypt.o getaddrinfo.o getpeereid.o inet_aton.o open.o system.o snprintf.o strerror.o strlcpy.o win32error.o win32setlocale.o, $(LIBOBJS)) # backend/libpq + backend_c = ip.c md5.c OBJS += ip.o md5.o # utils/mb OBJS += encnames.o wchar.o ifeq ($(with_openssl),yes) OBJS += fe-secure-openssl.o + # backend/libpq + backend_c += ssloptions.c + OBJS += ssloptions.o endif ifeq ($(PORTNAME), cygwin) *************** backend_src = $(top_srcdir)/src/backend *** 96,102 **** chklocale.c crypt.c getaddrinfo.c getpeereid.c inet_aton.c inet_net_ntop.c noblock.c open.c system.c pgsleep.c pgstrcasecmp.c pqsignal.c snprintf.c strerror.c strlcpy.c thread.c win32error.c win32setlocale.c: % : $(top_srcdir)/src/port/% rm -f $@ && $(LN_S) $< . ! ip.c md5.c: % : $(backend_src)/libpq/% rm -f $@ && $(LN_S) $< . encnames.c wchar.c: % : $(backend_src)/utils/mb/% --- 100,106 ---- chklocale.c crypt.c getaddrinfo.c getpeereid.c inet_aton.c inet_net_ntop.c noblock.c open.c system.c pgsleep.c pgstrcasecmp.c pqsignal.c snprintf.c strerror.c strlcpy.c thread.c win32error.c win32setlocale.c: % : $(top_srcdir)/src/port/% rm -f $@ && $(LN_S) $< . ! $(backend_c): % : $(backend_src)/libpq/% rm -f $@ && $(LN_S) $< . encnames.c wchar.c: % : $(backend_src)/utils/mb/% *************** clean distclean: clean-lib *** 156,162 **** rm -f inet_net_ntop.c noblock.c pgstrcasecmp.c pqsignal.c thread.c rm -f chklocale.c crypt.c getaddrinfo.c getpeereid.c inet_aton.c open.c system.c snprintf.c strerror.c strlcpy.c win32error.c win32setlocale.c rm -f pgsleep.c ! rm -f md5.c ip.c rm -f encnames.c wchar.c maintainer-clean: distclean maintainer-clean-lib --- 160,166 ---- rm -f inet_net_ntop.c noblock.c pgstrcasecmp.c pqsignal.c thread.c rm -f chklocale.c crypt.c getaddrinfo.c getpeereid.c inet_aton.c open.c system.c snprintf.c strerror.c strlcpy.c win32error.c win32setlocale.c rm -f pgsleep.c ! rm -f $(backend_c) rm -f encnames.c wchar.c maintainer-clean: distclean maintainer-clean-lib diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c new file mode 100644 index 3bac2bc..33dc826 *** a/src/interfaces/libpq/fe-connect.c --- b/src/interfaces/libpq/fe-connect.c *************** static const internalPQconninfoOption PQ *** 254,259 **** --- 254,263 ---- "SSL-Mode", "", 12, /* sizeof("verify-full") == 12 */ offsetof(struct pg_conn, sslmode)}, + {"sslprotocols", "PGSSLPROTOCOLS", "ALL:-SSL", NULL, + "SSL-Protocols", "", 10, + offsetof(struct pg_conn, sslprotocols)}, + {"sslcompression", "PGSSLCOMPRESSION", "1", NULL, "SSL-Compression", "", 1, offsetof(struct pg_conn, sslcompression)}, diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c new file mode 100644 index 7fcc1f0..85c405a *** a/src/interfaces/libpq/fe-secure-openssl.c --- b/src/interfaces/libpq/fe-secure-openssl.c *************** *** 29,34 **** --- 29,35 ---- #include "libpq-fe.h" #include "fe-auth.h" #include "libpq-int.h" + #include "libpq/ssloptions.h" #ifdef WIN32 #include "win32.h" *************** pgtls_init(PGconn *conn) *** 815,820 **** --- 816,823 ---- if (!SSL_context) { + long protocol_options; + if (pq_init_ssl_lib) { #if SSLEAY_VERSION_NUMBER >= 0x00907000L *************** pgtls_init(PGconn *conn) *** 845,852 **** return -1; } /* Disable old protocol versions */ ! SSL_CTX_set_options(SSL_context, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); /* * Disable OpenSSL's moving-write-buffer sanity check, because it --- 848,868 ---- return -1; } + /* set up the allowed protocol list */ + if (!options_from_ssl_protocols_string(conn->sslprotocols, + &protocol_options)) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("invalid SSL protocols list (are you missing 'ALL:' before '-BADPROTO'?)\n")); + #ifdef ENABLE_THREAD_SAFETY + pthread_mutex_unlock(&ssl_config_mutex); + #endif + return -1; + } + /* Disable old protocol versions */ ! protocol_options |= SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3; ! SSL_CTX_set_options(SSL_context, protocol_options); /* * Disable OpenSSL's moving-write-buffer sanity check, because it diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h new file mode 100644 index c345571..28015e9 *** a/src/interfaces/libpq/libpq-int.h --- b/src/interfaces/libpq/libpq-int.h *************** struct pg_conn *** 324,329 **** --- 324,330 ---- char *keepalives_count; /* maximum number of TCP keepalive * retransmits */ char *sslmode; /* SSL mode (require,prefer,allow,disable) */ + char *sslprotocols; /* SSL protocols (e.g, "ALL:-SSL") */ char *sslcompression; /* SSL compression (0 or 1) */ char *sslkey; /* client key filename */ char *sslcert; /* client certificate filename */ -- 2.1.0
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers