>From fdaabeb3b490981e9aac2f847495ef68c5a55baa Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Wed, 5 Oct 2016 12:08:01 +0300
Subject: [PATCH 1/1] Don't share SSL_CTX between libpq connections.

There were several issues with the old coding:

1. There were race conditions, if two threads opened a connection at
   the same time. We used a mutex around SSL_CTX_* calls, but that was
   not enough, e.g. if one thread SSL_CTX_load_verify_locations() with
   one path, and another thread set it with a different path, before the
   first thread got to establish the connection.

2. Opening two different connections, with different sslrootcert settings,
   seemed to fail outright with "SSL error: block type is not 01". Not
   sure why.

3. We created the SSL object, before calling SSL_CTX_load_verify_locations
   and SSL_CTX_use_certificate_chain_file on the SSL context. That was
   wrong, because the options set on the SSL context are propagated to
   the SSL object, when the SSL object is created. If they are set after
   the SSL object has already been created, they won't take effect until
   the next connection. (This is bug #14329)

At least some of these could've been fixed while still using a shared
context, but it would've been more complicated and error-prone. To keep
things simple, let's just use a separate SSL context for each connection,
and accept the overhead.

Report, analysis and test case by Kacper Zuk.

Discussion: <20160920101051.1355.79453@wrigleys.postgresql.org>
---
 src/interfaces/libpq/fe-secure-openssl.c | 397 +++++++++++++------------------
 src/test/ssl/Makefile                    |   6 +-
 src/test/ssl/README                      |   4 +
 src/test/ssl/ServerSetup.pm              |   6 +-
 src/test/ssl/ssl/client+client_ca.crt    |  25 ++
 src/test/ssl/t/001_ssltests.pl           |  10 +-
 6 files changed, 211 insertions(+), 237 deletions(-)
 create mode 100644 src/test/ssl/ssl/client+client_ca.crt

diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 2dcdce7..402db51 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -80,12 +80,7 @@ static int	my_SSL_set_fd(PGconn *conn, int fd);
 static bool pq_init_ssl_lib = true;
 static bool pq_init_crypto_lib = true;
 
-/*
- * SSL_context is currently shared between threads and therefore we need to be
- * careful to lock around any usage of it when providing thread safety.
- * ssl_config_mutex is the mutex that we use to protect it.
- */
-static SSL_CTX *SSL_context = NULL;
+static bool ssl_lib_initialized = false;
 
 #ifdef ENABLE_THREAD_SAFETY
 static long ssl_open_connections = 0;
@@ -133,44 +128,9 @@ pgtls_open_client(PGconn *conn)
 	/* First time through? */
 	if (conn->ssl == NULL)
 	{
-#ifdef ENABLE_THREAD_SAFETY
-		int			rc;
-#endif
-
-#ifdef ENABLE_THREAD_SAFETY
-		if ((rc = pthread_mutex_lock(&ssl_config_mutex)))
-		{
-			printfPQExpBuffer(&conn->errorMessage,
-			   libpq_gettext("could not acquire mutex: %s\n"), strerror(rc));
-			return PGRES_POLLING_FAILED;
-		}
-#endif
-		/* Create a connection-specific SSL object */
-		if (!(conn->ssl = SSL_new(SSL_context)) ||
-			!SSL_set_app_data(conn->ssl, conn) ||
-			!my_SSL_set_fd(conn, conn->sock))
-		{
-			char	   *err = SSLerrmessage(ERR_get_error());
-
-			printfPQExpBuffer(&conn->errorMessage,
-				   libpq_gettext("could not establish SSL connection: %s\n"),
-							  err);
-			SSLerrfree(err);
-#ifdef ENABLE_THREAD_SAFETY
-			pthread_mutex_unlock(&ssl_config_mutex);
-#endif
-			pgtls_close(conn);
-
-			return PGRES_POLLING_FAILED;
-		}
-		conn->ssl_in_use = true;
-
-#ifdef ENABLE_THREAD_SAFETY
-		pthread_mutex_unlock(&ssl_config_mutex);
-#endif
-
 		/*
-		 * Load client certificate, private key, and trusted CA certs.
+		 * Create a connection-specific SSL object, and load client certificate,
+		 * private key, and trusted CA certs.
 		 */
 		if (initialize_SSL(conn) != 0)
 		{
@@ -771,8 +731,7 @@ pq_lockingcallback(int mode, int n, const char *file, int line)
 #endif   /* ENABLE_THREAD_SAFETY && HAVE_CRYPTO_LOCK */
 
 /*
- * Initialize SSL system, in particular creating the SSL_context object
- * that will be shared by all SSL-using connections in this process.
+ * Initialize SSL library.
  *
  * In threadsafe mode, this includes setting up libcrypto callback functions
  * to do thread locking.
@@ -851,7 +810,7 @@ pgtls_init(PGconn *conn)
 #endif   /* HAVE_CRYPTO_LOCK */
 #endif   /* ENABLE_THREAD_SAFETY */
 
-	if (!SSL_context)
+	if (!ssl_lib_initialized)
 	{
 		if (pq_init_ssl_lib)
 		{
@@ -863,36 +822,7 @@ pgtls_init(PGconn *conn)
 			SSL_load_error_strings();
 #endif
 		}
-
-		/*
-		 * We use SSLv23_method() because it can negotiate use of the highest
-		 * mutually supported protocol version, while alternatives like
-		 * TLSv1_2_method() permit only one specific version.  Note that we
-		 * don't actually allow SSL v2 or v3, only TLS protocols (see below).
-		 */
-		SSL_context = SSL_CTX_new(SSLv23_method());
-		if (!SSL_context)
-		{
-			char	   *err = SSLerrmessage(ERR_get_error());
-
-			printfPQExpBuffer(&conn->errorMessage,
-						 libpq_gettext("could not create SSL context: %s\n"),
-							  err);
-			SSLerrfree(err);
-#ifdef ENABLE_THREAD_SAFETY
-			pthread_mutex_unlock(&ssl_config_mutex);
-#endif
-			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
-		 * causes unnecessary failures in nonblocking send cases.
-		 */
-		SSL_CTX_set_mode(SSL_context, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
+		ssl_lib_initialized = true;
 	}
 
 #ifdef ENABLE_THREAD_SAFETY
@@ -936,9 +866,8 @@ destroy_ssl_system(void)
 			CRYPTO_set_id_callback(NULL);
 
 		/*
-		 * We don't free the lock array or the SSL_context. If we get another
-		 * connection in this process, we will just re-use them with the
-		 * existing mutexes.
+		 * We don't free the lock array. If we get another connection in
+		 * this process, we will just re-use them with the existing mutexes.
 		 *
 		 * This means we leak a little memory on repeated load/unload of the
 		 * library.
@@ -950,26 +879,22 @@ destroy_ssl_system(void)
 }
 
 /*
- *	Initialize (potentially) per-connection SSL data, namely the
- *	client certificate, private key, and trusted CA certs.
- *
- *	conn->ssl must already be created.  It receives the connection's client
- *	certificate and private key.  Note however that certificates also get
- *	loaded into the SSL_context object, and are therefore accessible to all
- *	connections in this process.  This should be OK as long as there aren't
- *	any hash collisions among the certs.
+ *	Creates per-connection SSL object, and loads the client certificate,
+ *	private key, and trusted CA certs.
  *
  *	Returns 0 if OK, -1 on failure (with a message in conn->errorMessage).
  */
 static int
 initialize_SSL(PGconn *conn)
 {
+	SSL_CTX	   *SSL_context;
 	struct stat buf;
 	char		homedir[MAXPGPATH];
 	char		fnbuf[MAXPGPATH];
 	char		sebuf[256];
 	bool		have_homedir;
 	bool		have_cert;
+	bool		have_rootcert;
 	EVP_PKEY   *pkey = NULL;
 
 	/*
@@ -985,6 +910,123 @@ initialize_SSL(PGconn *conn)
 	else	/* won't need it */
 		have_homedir = false;
 
+	/*
+	 * Create a new SSL_CTX object.
+	 *
+	 * We used to share a single SSL_CTX between all connections, but it was
+	 * complicated if connections used different certificates. So now we create
+	 * a separate context for each connection, and accept the overhead.
+	 */
+	SSL_context = SSL_CTX_new(SSLv23_method());
+	if (!SSL_context)
+	{
+		char	   *err = SSLerrmessage(ERR_get_error());
+
+		printfPQExpBuffer(&conn->errorMessage,
+						 libpq_gettext("could not create SSL context: %s\n"),
+							  err);
+		SSLerrfree(err);
+		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
+	 * causes unnecessary failures in nonblocking send cases.
+	 */
+	SSL_CTX_set_mode(SSL_context, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
+
+	/*
+	 * If the root cert file exists, load it so we can perform certificate
+	 * verification. If sslmode is "verify-full" we will also do further
+	 * verification after the connection has been completed.
+	 */
+	if (conn->sslrootcert && strlen(conn->sslrootcert) > 0)
+		strlcpy(fnbuf, conn->sslrootcert, sizeof(fnbuf));
+	else if (have_homedir)
+		snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, ROOT_CERT_FILE);
+	else
+		fnbuf[0] = '\0';
+
+	if (fnbuf[0] != '\0' &&
+		stat(fnbuf, &buf) == 0)
+	{
+		X509_STORE *cvstore;
+
+		if (SSL_CTX_load_verify_locations(SSL_context, fnbuf, NULL) != 1)
+		{
+			char	   *err = SSLerrmessage(ERR_get_error());
+
+			printfPQExpBuffer(&conn->errorMessage,
+							  libpq_gettext("could not read root certificate file \"%s\": %s\n"),
+							  fnbuf, err);
+			SSLerrfree(err);
+			SSL_CTX_free(SSL_context);
+			return -1;
+		}
+
+		if ((cvstore = SSL_CTX_get_cert_store(SSL_context)) != NULL)
+		{
+			if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+				strlcpy(fnbuf, conn->sslcrl, sizeof(fnbuf));
+			else if (have_homedir)
+				snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, ROOT_CRL_FILE);
+			else
+				fnbuf[0] = '\0';
+
+			/* Set the flags to check against the complete CRL chain */
+			if (fnbuf[0] != '\0' &&
+				X509_STORE_load_locations(cvstore, fnbuf, NULL) == 1)
+			{
+				/* OpenSSL 0.96 does not support X509_V_FLAG_CRL_CHECK */
+#ifdef X509_V_FLAG_CRL_CHECK
+				X509_STORE_set_flags(cvstore,
+						  X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL);
+#else
+				char	   *err = SSLerrmessage(ERR_get_error());
+
+				printfPQExpBuffer(&conn->errorMessage,
+								  libpq_gettext("SSL library does not support CRL certificates (file \"%s\")\n"),
+								  fnbuf);
+				SSLerrfree(err);
+				SSL_CTX_free(SSL_context);
+				return -1;
+#endif
+			}
+			/* if not found, silently ignore;  we do not require CRL */
+		}
+		have_rootcert = true;
+	}
+	else
+	{
+		/*
+		 * stat() failed; assume root file doesn't exist.  If sslmode is
+		 * verify-ca or verify-full, this is an error.  Otherwise, continue
+		 * without performing any server cert verification.
+		 */
+		if (conn->sslmode[0] == 'v')	/* "verify-ca" or "verify-full" */
+		{
+			/*
+			 * The only way to reach here with an empty filename is if
+			 * pqGetHomeDirectory failed.  That's a sufficiently unusual case
+			 * that it seems worth having a specialized error message for it.
+			 */
+			if (fnbuf[0] == '\0')
+				printfPQExpBuffer(&conn->errorMessage,
+								  libpq_gettext("could not get home directory to locate root certificate file\n"
+												"Either provide the file or change sslmode to disable server certificate verification.\n"));
+			else
+				printfPQExpBuffer(&conn->errorMessage,
+				libpq_gettext("root certificate file \"%s\" does not exist\n"
+							  "Either provide the file or change sslmode to disable server certificate verification.\n"), fnbuf);
+			SSL_CTX_free(SSL_context);
+			return -1;
+		}
+		have_rootcert = false;
+	}
+
 	/* Read the client certificate file */
 	if (conn->sslcert && strlen(conn->sslcert) > 0)
 		strlcpy(fnbuf, conn->sslcert, sizeof(fnbuf));
@@ -1010,6 +1052,7 @@ initialize_SSL(PGconn *conn)
 			printfPQExpBuffer(&conn->errorMessage,
 			   libpq_gettext("could not open certificate file \"%s\": %s\n"),
 							  fnbuf, pqStrerror(errno, sebuf, sizeof(sebuf)));
+			SSL_CTX_free(SSL_context);
 			return -1;
 		}
 		have_cert = false;
@@ -1017,31 +1060,10 @@ initialize_SSL(PGconn *conn)
 	else
 	{
 		/*
-		 * Cert file exists, so load it.  Since OpenSSL doesn't provide the
-		 * equivalent of "SSL_use_certificate_chain_file", we actually have to
-		 * load the file twice.  The first call loads any extra certs after
-		 * the first one into chain-cert storage associated with the
-		 * SSL_context.  The second call loads the first cert (only) into the
-		 * SSL object, where it will be correctly paired with the private key
-		 * we load below.  We do it this way so that each connection
-		 * understands which subject cert to present, in case different
-		 * sslcert settings are used for different connections in the same
-		 * process.
-		 *
-		 * NOTE: This function may also modify our SSL_context and therefore
-		 * we have to lock around this call and any places where we use the
-		 * SSL_context struct.
+		 * Cert file exists, so load it. Since OpenSSL doesn't provide the
+		 * equivalent of "SSL_use_certificate_chain_file", we have to load
+		 * it into the SSL context, rather than the SSL object.
 		 */
-#ifdef ENABLE_THREAD_SAFETY
-		int			rc;
-
-		if ((rc = pthread_mutex_lock(&ssl_config_mutex)))
-		{
-			printfPQExpBuffer(&conn->errorMessage,
-			   libpq_gettext("could not acquire mutex: %s\n"), strerror(rc));
-			return -1;
-		}
-#endif
 		if (SSL_CTX_use_certificate_chain_file(SSL_context, fnbuf) != 1)
 		{
 			char	   *err = SSLerrmessage(ERR_get_error());
@@ -1050,34 +1072,42 @@ initialize_SSL(PGconn *conn)
 			   libpq_gettext("could not read certificate file \"%s\": %s\n"),
 							  fnbuf, err);
 			SSLerrfree(err);
-
-#ifdef ENABLE_THREAD_SAFETY
-			pthread_mutex_unlock(&ssl_config_mutex);
-#endif
-			return -1;
-		}
-
-		if (SSL_use_certificate_file(conn->ssl, fnbuf, SSL_FILETYPE_PEM) != 1)
-		{
-			char	   *err = SSLerrmessage(ERR_get_error());
-
-			printfPQExpBuffer(&conn->errorMessage,
-			   libpq_gettext("could not read certificate file \"%s\": %s\n"),
-							  fnbuf, err);
-			SSLerrfree(err);
-#ifdef ENABLE_THREAD_SAFETY
-			pthread_mutex_unlock(&ssl_config_mutex);
-#endif
+			SSL_CTX_free(SSL_context);
 			return -1;
 		}
 
 		/* need to load the associated private key, too */
 		have_cert = true;
+	}
 
-#ifdef ENABLE_THREAD_SAFETY
-		pthread_mutex_unlock(&ssl_config_mutex);
-#endif
+	/*
+	 * The SSL context is now loaded the correct root and client certificates.
+	 * Create a connection-specific SSL object. The private key is loaded directly
+	 * into the SSL object. (We could load the private key into the context, too, but
+	 * we have done it this way historically, and it doesn't really matter.)
+	 */
+	if (!(conn->ssl = SSL_new(SSL_context)) ||
+		!SSL_set_app_data(conn->ssl, conn) ||
+		!my_SSL_set_fd(conn, conn->sock))
+	{
+		char	   *err = SSLerrmessage(ERR_get_error());
+
+		printfPQExpBuffer(&conn->errorMessage,
+				   libpq_gettext("could not establish SSL connection: %s\n"),
+						  err);
+		SSLerrfree(err);
+		SSL_CTX_free(SSL_context);
+		return -1;
 	}
+	conn->ssl_in_use = true;
+
+	/*
+	 * SSL contexts are reference counted by OpenSSL. We can free it as soon as we
+	 * have created the SSL object, and it will stick around for as long as it's
+	 * actually needed.
+	 */
+	SSL_CTX_free(SSL_context);
+	SSL_context = NULL;
 
 	/*
 	 * Read the SSL key. If a key is specified, treat it as an engine:key
@@ -1236,109 +1266,10 @@ initialize_SSL(PGconn *conn)
 	}
 
 	/*
-	 * If the root cert file exists, load it so we can perform certificate
-	 * verification. If sslmode is "verify-full" we will also do further
-	 * verification after the connection has been completed.
+	 * If a root cert was loaded, also set our certificate verification callback.
 	 */
-	if (conn->sslrootcert && strlen(conn->sslrootcert) > 0)
-		strlcpy(fnbuf, conn->sslrootcert, sizeof(fnbuf));
-	else if (have_homedir)
-		snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, ROOT_CERT_FILE);
-	else
-		fnbuf[0] = '\0';
-
-	if (fnbuf[0] != '\0' &&
-		stat(fnbuf, &buf) == 0)
-	{
-		X509_STORE *cvstore;
-
-#ifdef ENABLE_THREAD_SAFETY
-		int			rc;
-
-		if ((rc = pthread_mutex_lock(&ssl_config_mutex)))
-		{
-			printfPQExpBuffer(&conn->errorMessage,
-			   libpq_gettext("could not acquire mutex: %s\n"), strerror(rc));
-			return -1;
-		}
-#endif
-		if (SSL_CTX_load_verify_locations(SSL_context, fnbuf, NULL) != 1)
-		{
-			char	   *err = SSLerrmessage(ERR_get_error());
-
-			printfPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("could not read root certificate file \"%s\": %s\n"),
-							  fnbuf, err);
-			SSLerrfree(err);
-#ifdef ENABLE_THREAD_SAFETY
-			pthread_mutex_unlock(&ssl_config_mutex);
-#endif
-			return -1;
-		}
-
-		if ((cvstore = SSL_CTX_get_cert_store(SSL_context)) != NULL)
-		{
-			if (conn->sslcrl && strlen(conn->sslcrl) > 0)
-				strlcpy(fnbuf, conn->sslcrl, sizeof(fnbuf));
-			else if (have_homedir)
-				snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, ROOT_CRL_FILE);
-			else
-				fnbuf[0] = '\0';
-
-			/* Set the flags to check against the complete CRL chain */
-			if (fnbuf[0] != '\0' &&
-				X509_STORE_load_locations(cvstore, fnbuf, NULL) == 1)
-			{
-				/* OpenSSL 0.96 does not support X509_V_FLAG_CRL_CHECK */
-#ifdef X509_V_FLAG_CRL_CHECK
-				X509_STORE_set_flags(cvstore,
-						  X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL);
-#else
-				char	   *err = SSLerrmessage(ERR_get_error());
-
-				printfPQExpBuffer(&conn->errorMessage,
-								  libpq_gettext("SSL library does not support CRL certificates (file \"%s\")\n"),
-								  fnbuf);
-				SSLerrfree(err);
-#ifdef ENABLE_THREAD_SAFETY
-				pthread_mutex_unlock(&ssl_config_mutex);
-#endif
-				return -1;
-#endif
-			}
-			/* if not found, silently ignore;  we do not require CRL */
-		}
-#ifdef ENABLE_THREAD_SAFETY
-		pthread_mutex_unlock(&ssl_config_mutex);
-#endif
-
+	if (have_rootcert)
 		SSL_set_verify(conn->ssl, SSL_VERIFY_PEER, verify_cb);
-	}
-	else
-	{
-		/*
-		 * stat() failed; assume root file doesn't exist.  If sslmode is
-		 * verify-ca or verify-full, this is an error.  Otherwise, continue
-		 * without performing any server cert verification.
-		 */
-		if (conn->sslmode[0] == 'v')	/* "verify-ca" or "verify-full" */
-		{
-			/*
-			 * The only way to reach here with an empty filename is if
-			 * pqGetHomeDirectory failed.  That's a sufficiently unusual case
-			 * that it seems worth having a specialized error message for it.
-			 */
-			if (fnbuf[0] == '\0')
-				printfPQExpBuffer(&conn->errorMessage,
-								  libpq_gettext("could not get home directory to locate root certificate file\n"
-												"Either provide the file or change sslmode to disable server certificate verification.\n"));
-			else
-				printfPQExpBuffer(&conn->errorMessage,
-				libpq_gettext("root certificate file \"%s\" does not exist\n"
-							  "Either provide the file or change sslmode to disable server certificate verification.\n"), fnbuf);
-			return -1;
-		}
-	}
 
 	/*
 	 * If the OpenSSL version used supports it (from 1.0.0 on) and the user
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 3d992ba..2b04d82 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -23,7 +23,8 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
 	ssl/client.crl ssl/server.crl ssl/root.crl \
 	ssl/both-cas-1.crt ssl/both-cas-2.crt \
 	ssl/root+server_ca.crt ssl/root+server.crl \
-	ssl/root+client_ca.crt ssl/root+client.crl
+	ssl/root+client_ca.crt ssl/root+client.crl \
+	ssl/client+client_ca.crt
 
 # This target generates all the key and certificate files.
 sslfiles: $(SSLFILES)
@@ -99,6 +100,9 @@ ssl/root+server_ca.crt: ssl/root_ca.crt ssl/server_ca.crt
 ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
 	cat $^ > $@
 
+ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
+	cat $^ > $@
+
 #### CRLs
 
 ssl/client.crl: ssl/client-revoked.crt
diff --git a/src/test/ssl/README b/src/test/ssl/README
index 52bd68f..50fa14e 100644
--- a/src/test/ssl/README
+++ b/src/test/ssl/README
@@ -65,6 +65,10 @@ root+server_ca
 root+client_ca
 	Contains root_crt and client_ca.crt. For use as server's "ssl_ca_file".
 
+client+client_ca
+	Contains client.crt and client_ca.crt in that order. For use as client's
+	certificate chain.
+
 There are also CRLs for each of the CAs: root.crl, server.crl and client.crl.
 
 For convenience, all of these keypairs and certificates are included in the
diff --git a/src/test/ssl/ServerSetup.pm b/src/test/ssl/ServerSetup.pm
index 4e93184..d312880 100644
--- a/src/test/ssl/ServerSetup.pm
+++ b/src/test/ssl/ServerSetup.pm
@@ -75,6 +75,7 @@ sub configure_test_server_for_ssl
 	copy_files("ssl/server-*.key", $pgdata);
 	chmod(0600, glob "$pgdata/server-*.key") or die $!;
 	copy_files("ssl/root+client_ca.crt", $pgdata);
+	copy_files("ssl/root_ca.crt", $pgdata);
 	copy_files("ssl/root+client.crl",    $pgdata);
 
   # Only accept SSL connections from localhost. Our tests don't depend on this
@@ -101,13 +102,14 @@ sub switch_server_cert
 {
 	my $node     = $_[0];
 	my $certfile = $_[1];
+	my $cafile = $_[2] || "root+client_ca";
 	my $pgdata   = $node->data_dir;
 
-	diag "Restarting server with certfile \"$certfile\"...";
+	diag "Restarting server with certfile \"$certfile\" and cafile \"$cafile\"...";
 
 	open SSLCONF, ">$pgdata/sslconfig.conf";
 	print SSLCONF "ssl=on\n";
-	print SSLCONF "ssl_ca_file='root+client_ca.crt'\n";
+	print SSLCONF "ssl_ca_file='$cafile.crt'\n";
 	print SSLCONF "ssl_cert_file='$certfile.crt'\n";
 	print SSLCONF "ssl_key_file='$certfile.key'\n";
 	print SSLCONF "ssl_crl_file='root+client.crl'\n";
diff --git a/src/test/ssl/ssl/client+client_ca.crt b/src/test/ssl/ssl/client+client_ca.crt
new file mode 100644
index 0000000..3caada6
--- /dev/null
+++ b/src/test/ssl/ssl/client+client_ca.crt
@@ -0,0 +1,25 @@
+-----BEGIN CERTIFICATE-----
+MIIBxzCCATACAQEwDQYJKoZIhvcNAQEFBQAwQjFAMD4GA1UEAww3VGVzdCBDQSBm
+b3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IGNsaWVudCBjZXJ0czAe
+Fw0xNjA5MTIxNjMwMDFaFw00NDAxMjkxNjMwMDFaMBYxFDASBgNVBAMMC3NzbHRl
+c3R1c2VyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDN3RFl8VWMEBN1Qas0
+w1CFcXdDEbKVNSPsqWHzHIEPoGJv+eUIBK2lQ/Ce8nRCdelO50RsmlbcXBIrjVl6
+BN0RmEeEVclgCdiamYN53LBdc5KWKpKCKn45lCtlZodWt0hNNx1pAmh85jDKpoO9
+ErbCnSU1wODPqnOzdkLU7jBu5QIDAQABMA0GCSqGSIb3DQEBBQUAA4GBABUz+vnu
+dD1Q1N/Ezs5DzJeQDtiJb9PNzBHAUPQoXeLvuITcDdyYWc18Yi4fX7gwyD42q2iu
+1I0hmm2bNJfujsGbvGYFLuQ4hC2ucAAj2Gm681GhhaNYtfsfHYm9R8GRZFvp40oj
+qXpkDkYsPdyVxUyoxJ+M0Ub5VC/k1pQNtIaq
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICCDCCAXGgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBAMT4wPAYDVQQDDDVUZXN0
+IHJvb3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBzdWl0
+ZTAeFw0xNjA5MTIxNjMwMDFaFw00NDAxMjkxNjMwMDFaMEIxQDA+BgNVBAMMN1Rl
+c3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBjbGllbnQg
+Y2VydHMwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMI2MXWSb8TZnCLVNYJ+
+19b4noxRmaR1W2zUxl4aTMfiPt9cK06lNY39EPBfjmb7hjxD76w8fLoV/aZ0gOgd
+JXFRZvIg7SyM7QVFma0AJAIZayes+ba1odEmBEi378g0mLrjCLqZtBVHfvJxL/6x
+6/flSTAn/+09vtELvvLWBePZAgMBAAGjEDAOMAwGA1UdEwQFMAMBAf8wDQYJKoZI
+hvcNAQEFBQADgYEAlGC24V2TsiSlo9RIboBZTZqd0raUpKkmVbkwKyqcmecoFfCI
+TCmoyJLYyUL5/e3dtn/cGDcaqxaO3qxnstxVEMSrlCGfZdZJ2oouXZMpDy9CkeOM
+ypCCx9pc4EmP3mvu64f21+dNCXlhM36pZ1IokeS5jk2FIHUda+m5jlk5o6I=
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 80e8ea1..dc8e064 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -2,7 +2,7 @@ use strict;
 use warnings;
 use PostgresNode;
 use TestLib;
-use Test::More tests => 38;
+use Test::More tests => 40;
 use ServerSetup;
 use File::Copy;
 
@@ -239,3 +239,11 @@ test_connect_fails(
 test_connect_fails(
 "user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked.key"
 );
+
+# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
+switch_server_cert($node, 'server-cn-only', 'root_ca');
+$common_connstr =
+"user=ssltestuser dbname=certdb sslkey=ssl/client.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+
+test_connect_ok("sslmode=require sslcert=ssl/client+client_ca.crt");
+test_connect_fails("sslmode=require sslcert=ssl/client.crt");
-- 
2.9.3

