aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/interfaces/libpq/fe-secure-openssl.c397
-rw-r--r--src/test/ssl/Makefile6
-rw-r--r--src/test/ssl/README4
-rw-r--r--src/test/ssl/ServerSetup.pm6
-rw-r--r--src/test/ssl/ssl/client+client_ca.crt25
-rw-r--r--src/test/ssl/t/001_ssltests.pl9
6 files changed, 210 insertions, 237 deletions
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 64474e34183..fc723cc083b 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -82,12 +82,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;
@@ -135,44 +130,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)
{
@@ -772,8 +732,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.
@@ -852,7 +811,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)
{
@@ -866,36 +825,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
@@ -939,9 +869,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.
@@ -953,26 +882,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.
+ * Create per-connection SSL object, and load 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;
/*
@@ -988,6 +913,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));
@@ -1013,6 +1055,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;
@@ -1020,31 +1063,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());
@@ -1053,34 +1075,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 with 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
@@ -1239,109 +1269,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 385ef51b474..72919d09e2a 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 52bd68f49fa..50fa14e287e 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 65575e24f14..cbdb44e67a6 100644
--- a/src/test/ssl/ServerSetup.pm
+++ b/src/test/ssl/ServerSetup.pm
@@ -72,6 +72,7 @@ sub configure_test_server_for_ssl
copy_files("ssl/server-*.key", "$tempdir/pgdata");
chmod(0600, glob "$tempdir/pgdata/server-*.key") or die $!;
copy_files("ssl/root+client_ca.crt", "$tempdir/pgdata");
+ copy_files("ssl/root_ca.crt", "$tempdir/pgdata");
copy_files("ssl/root+client.crl", "$tempdir/pgdata");
# Only accept SSL connections from localhost. Our tests don't depend on this
@@ -98,12 +99,13 @@ sub switch_server_cert
{
my $tempdir = $_[0];
my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
- diag "Restarting server with certfile \"$certfile\"...";
+ diag "Restarting server with certfile \"$certfile\" and cafile \"$cafile\"...";
open SSLCONF, ">$tempdir/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 00000000000..3caada693de
--- /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 29ff8c590fe..48106bc9d96 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -1,7 +1,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 38;
+use Test::More tests => 40;
use ServerSetup;
use File::Copy;
@@ -242,6 +242,13 @@ 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($tempdir, '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");
# All done! Save the log, before the temporary installation is deleted
copy("$tempdir/client-log", "./client-log");