]> granicus.if.org Git - postgresql/commitdiff
Rearrange libpq's SSL initialization to simplify it and make it handle some
authorTom Lane <tgl@sss.pgh.pa.us>
Wed, 26 May 2010 21:39:27 +0000 (21:39 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Wed, 26 May 2010 21:39:27 +0000 (21:39 +0000)
additional cases correctly.  The original coding failed to load additional
(chain) certificates from the client cert file, meaning that indirectly signed
client certificates didn't work unless one hacked the server's root.crt file
to include intermediate CAs (not the desired approach).  Another problem was
that everything got loaded into the shared SSL_context object, which meant
that concurrent connections trying to use different sslcert settings could
well fail due to conflicting over the single available slot for a keyed
certificate.

To fix, get rid of the use of SSL_CTX_set_client_cert_cb(), which is
deprecated anyway in the OpenSSL documentation, and instead just
unconditionally load the client cert and private key during connection
initialization.  This lets us use SSL_CTX_use_certificate_chain_file(),
which does the right thing with additional certs, and is lots simpler than
the previous hacking about with BIO-level access.  A small disadvantage is
that we have to load the primary client cert a second time with
SSL_use_certificate_file, so that that one ends up in the correct slot
within the connection's SSL object where it can get paired with the key.
Given the other overhead of making an SSL connection, that doesn't seem
worth worrying about.

Per discussion ensuing from bug #5468.

src/interfaces/libpq/fe-connect.c
src/interfaces/libpq/fe-secure.c

index 54db2b7b8962b61c3512d8e04c9230b633d33d2f..3a1465457e08f2e3c7c326a1ed7f0f8d3ddf8bc4 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/interfaces/libpq/fe-connect.c,v 1.392 2010/04/30 17:09:13 tgl Exp $
+ *       $PostgreSQL: pgsql/src/interfaces/libpq/fe-connect.c,v 1.393 2010/05/26 21:39:27 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1623,7 +1623,7 @@ keep_going:                                               /* We will come back to here until there is
                                        if (SSLok == 'S')
                                        {
                                                /* Set up global SSL state if required */
-                                               if (pqsecure_initialize(conn) == -1)
+                                               if (pqsecure_initialize(conn) != 0)
                                                        goto error_return;
                                        }
                                        else if (SSLok == 'N')
index 23ac0f3893b854bd79a2fea70e78a5d9269fb03c..a8acf4e5cb176743abed7126798f3edf693e8293 100644 (file)
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/interfaces/libpq/fe-secure.c,v 1.133 2010/05/25 22:03:27 tgl Exp $
+ *       $PostgreSQL: pgsql/src/interfaces/libpq/fe-secure.c,v 1.134 2010/05/26 21:39:27 tgl Exp $
  *
  * NOTES
  *
  *       We don't provide informational callbacks here (like
- *       info_cb() in be-secure.c), since there's mechanism to
- *       display that information to the client.
+ *       info_cb() in be-secure.c), since there's no good mechanism to
+ *       display such information to the user.
  *
  *-------------------------------------------------------------------------
  */
@@ -59,7 +59,6 @@
 #ifdef USE_SSL
 
 #include <openssl/ssl.h>
-#include <openssl/bio.h>
 #if (SSLEAY_VERSION_NUMBER >= 0x00907000L)
 #include <openssl/conf.h>
 #endif
 #define ROOT_CRL_FILE          "root.crl"
 #endif
 
-#ifndef HAVE_ERR_SET_MARK
-/* These don't exist in OpenSSL before 0.9.8 */
-#define ERR_set_mark()         ((void) 0)
-#define ERR_pop_to_mark()      ((void) 0)
-#endif
-
 static bool verify_peer_name_matches_certificate(PGconn *);
 static int     verify_cb(int ok, X509_STORE_CTX *ctx);
-static int     client_cert_cb(SSL *, X509 **, EVP_PKEY **);
 static int     init_ssl_system(PGconn *conn);
 static void destroy_ssl_system(void);
-static int     initialize_SSL(PGconn *);
+static int     initialize_SSL(PGconn *conn);
 static void destroySSL(void);
 static PostgresPollingStatusType open_client_SSL(PGconn *);
 static void close_SSL(PGconn *);
@@ -224,7 +216,7 @@ PQinitOpenSSL(int do_ssl, int do_crypto)
 }
 
 /*
- *     Initialize global context
+ *     Initialize global SSL context
  */
 int
 pqsecure_initialize(PGconn *conn)
@@ -232,7 +224,7 @@ pqsecure_initialize(PGconn *conn)
        int                     r = 0;
 
 #ifdef USE_SSL
-       r = initialize_SSL(conn);
+       r = init_ssl_system(conn);
 #endif
 
        return r;
@@ -250,7 +242,7 @@ pqsecure_destroy(void)
 }
 
 /*
- *     Attempt to negotiate secure session.
+ *     Begin or continue negotiating a secure session.
  */
 PostgresPollingStatusType
 pqsecure_open_client(PGconn *conn)
@@ -262,6 +254,7 @@ pqsecure_open_client(PGconn *conn)
                /* We cannot use MSG_NOSIGNAL to block SIGPIPE when using SSL */
                conn->sigpipe_flag = false;
 
+               /* Create a connection-specific SSL object */
                if (!(conn->ssl = SSL_new(SSL_context)) ||
                        !SSL_set_app_data(conn->ssl, conn) ||
                        !SSL_set_fd(conn->ssl, conn->sock))
@@ -277,11 +270,16 @@ pqsecure_open_client(PGconn *conn)
                }
 
                /*
-                * Initialize errorMessage to empty.  This allows open_client_SSL() to
-                * detect whether client_cert_cb() has stored a message.
+                * Load client certificate, private key, and trusted CA certs.
                 */
-               resetPQExpBuffer(&conn->errorMessage);
+               if (initialize_SSL(conn) != 0)
+               {
+                       /* initialize_SSL already put a message in conn->errorMessage */
+                       close_SSL(conn);
+                       return PGRES_POLLING_FAILED;
+               }
        }
+
        /* Begin or continue the actual handshake */
        return open_client_SSL(conn);
 #else
@@ -545,7 +543,7 @@ verify_cb(int ok, X509_STORE_CTX *ctx)
  * This is roughly in line with RFC2818, but contrary to what most browsers
  * appear to be implementing (point 3 being the difference)
  *
- * Matching is always cone case-insensitive, since DNS is case insensitive.
+ * Matching is always case-insensitive, since DNS is case insensitive.
  */
 static int
 wildcard_certificate_match(const char *pattern, const char *string)
@@ -606,7 +604,7 @@ verify_peer_name_matches_certificate(PGconn *conn)
        else
        {
                /*
-                * Connect by hostname.
+                * Compare CN to originally given hostname.
                 *
                 * XXX: Should support alternate names here
                 */
@@ -626,264 +624,6 @@ verify_peer_name_matches_certificate(PGconn *conn)
        }
 }
 
-/*
- *     Callback used by SSL to load client cert and key.
- *     This callback is only called when the server wants a
- *     client cert.
- *
- *     Since BIO functions can set OpenSSL error codes, we must
- *     reset the OpenSSL error stack on *every* exit from this
- *     function once we've started using BIO.
- *
- *     Must return 1 on success, 0 on no data or error.
- */
-static int
-client_cert_cb(SSL *ssl, X509 **x509, EVP_PKEY **pkey)
-{
-       char            homedir[MAXPGPATH];
-       struct stat buf;
-
-#ifndef WIN32
-       struct stat buf2;
-       FILE       *fp;
-#endif
-       char            fnbuf[MAXPGPATH];
-       BIO                *bio;
-       PGconn     *conn = (PGconn *) SSL_get_app_data(ssl);
-       char            sebuf[256];
-
-       /*
-        * If conn->sslcert  or conn->sslkey is not set, we don't need the home
-        * directory to find the required files.
-        */
-       if (!conn->sslcert || !conn->sslkey)
-       {
-               if (!pqGetHomeDirectory(homedir, sizeof(homedir)))
-               {
-                       printfPQExpBuffer(&conn->errorMessage,
-                                                         libpq_gettext("could not get home directory to locate client certificate files\n"));
-                       return 0;
-               }
-       }
-
-       /* read the user certificate */
-       if (conn->sslcert)
-               strncpy(fnbuf, conn->sslcert, sizeof(fnbuf));
-       else
-               snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_CERT_FILE);
-
-       /*
-        * OpenSSL <= 0.9.8 lacks error stack handling, which means it's likely to
-        * report wrong error messages if access to the cert file fails. Do our
-        * own check for the readability of the file to catch the majority of such
-        * problems before OpenSSL gets involved.
-        */
-#ifndef HAVE_ERR_SET_MARK
-       {
-               FILE       *fp2;
-
-               if ((fp2 = fopen(fnbuf, "r")) == NULL)
-               {
-                       printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("could not open certificate file \"%s\": %s\n"),
-                                                         fnbuf, pqStrerror(errno, sebuf, sizeof(sebuf)));
-                       return 0;
-               }
-               fclose(fp2);
-       }
-#endif
-
-       /* save OpenSSL error stack */
-       ERR_set_mark();
-
-       if ((bio = BIO_new_file(fnbuf, "r")) == NULL)
-       {
-               printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("could not open certificate file \"%s\": %s\n"),
-                                                 fnbuf, pqStrerror(errno, sebuf, sizeof(sebuf)));
-               ERR_pop_to_mark();
-               return 0;
-       }
-
-       if (PEM_read_bio_X509(bio, x509, NULL, NULL) == NULL)
-       {
-               char       *err = SSLerrmessage();
-
-               printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("could not read certificate file \"%s\": %s\n"),
-                                                 fnbuf, err);
-               SSLerrfree(err);
-               BIO_free(bio);
-               ERR_pop_to_mark();
-               return 0;
-       }
-
-       BIO_free(bio);
-
-       /*
-        * Read the SSL key. If a key is specified, treat it as an engine:key
-        * combination if there is colon present - we don't support files with
-        * colon in the name. The exception is if the second character is a colon,
-        * in which case it can be a Windows filename with drive specification.
-        */
-       if (conn->sslkey && strlen(conn->sslkey) > 0)
-       {
-#ifdef USE_SSL_ENGINE
-               if (strchr(conn->sslkey, ':')
-#ifdef WIN32
-                       && conn->sslkey[1] != ':'
-#endif
-                       )
-               {
-                       /* Colon, but not in second character, treat as engine:key */
-                       char       *engine_str = strdup(conn->sslkey);
-                       char       *engine_colon = strchr(engine_str, ':');
-
-                       *engine_colon = '\0';           /* engine_str now has engine name */
-                       engine_colon++;         /* engine_colon now has key name */
-
-                       conn->engine = ENGINE_by_id(engine_str);
-                       if (conn->engine == NULL)
-                       {
-                               char       *err = SSLerrmessage();
-
-                               printfPQExpBuffer(&conn->errorMessage,
-                                        libpq_gettext("could not load SSL engine \"%s\": %s\n"),
-                                                                 engine_str, err);
-                               SSLerrfree(err);
-                               free(engine_str);
-                               ERR_pop_to_mark();
-                               return 0;
-                       }
-
-                       if (ENGINE_init(conn->engine) == 0)
-                       {
-                               char       *err = SSLerrmessage();
-
-                               printfPQExpBuffer(&conn->errorMessage,
-                               libpq_gettext("could not initialize SSL engine \"%s\": %s\n"),
-                                                                 engine_str, err);
-                               SSLerrfree(err);
-                               ENGINE_free(conn->engine);
-                               conn->engine = NULL;
-                               free(engine_str);
-                               ERR_pop_to_mark();
-                               return 0;
-                       }
-
-                       *pkey = ENGINE_load_private_key(conn->engine, engine_colon,
-                                                                                       NULL, NULL);
-                       if (*pkey == NULL)
-                       {
-                               char       *err = SSLerrmessage();
-
-                               printfPQExpBuffer(&conn->errorMessage,
-                                                                 libpq_gettext("could not read private SSL key \"%s\" from engine \"%s\": %s\n"),
-                                                                 engine_colon, engine_str, err);
-                               SSLerrfree(err);
-                               ENGINE_finish(conn->engine);
-                               ENGINE_free(conn->engine);
-                               conn->engine = NULL;
-                               free(engine_str);
-                               ERR_pop_to_mark();
-                               return 0;
-                       }
-                       free(engine_str);
-
-                       fnbuf[0] = '\0';        /* indicate we're not going to load from a
-                                                                * file */
-               }
-               else
-#endif   /* support for SSL engines */
-               {
-                       /* PGSSLKEY is not an engine, treat it as a filename */
-                       strncpy(fnbuf, conn->sslkey, sizeof(fnbuf));
-               }
-       }
-       else
-       {
-               /* No PGSSLKEY specified, load default file */
-               snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_KEY_FILE);
-       }
-
-       if (fnbuf[0] != '\0')
-       {
-               /* read the user key from file */
-
-               if (stat(fnbuf, &buf) != 0)
-               {
-                       printfPQExpBuffer(&conn->errorMessage,
-                                                         libpq_gettext("certificate present, but not private key file \"%s\"\n"),
-                                                         fnbuf);
-                       ERR_pop_to_mark();
-                       return 0;
-               }
-#ifndef WIN32
-               if (!S_ISREG(buf.st_mode) || buf.st_mode & (S_IRWXG | S_IRWXO))
-               {
-                       printfPQExpBuffer(&conn->errorMessage,
-                                                         libpq_gettext("private key file \"%s\" has group or world access; permissions should be u=rw (0600) or less\n"),
-                                                         fnbuf);
-                       ERR_pop_to_mark();
-                       return 0;
-               }
-#endif
-
-               if ((bio = BIO_new_file(fnbuf, "r")) == NULL)
-               {
-                       printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("could not open private key file \"%s\": %s\n"),
-                                                         fnbuf, pqStrerror(errno, sebuf, sizeof(sebuf)));
-                       ERR_pop_to_mark();
-                       return 0;
-               }
-#ifndef WIN32
-               BIO_get_fp(bio, &fp);
-               if (fstat(fileno(fp), &buf2) == -1 ||
-                       buf.st_dev != buf2.st_dev || buf.st_ino != buf2.st_ino)
-               {
-                       printfPQExpBuffer(&conn->errorMessage,
-                                                         libpq_gettext("private key file \"%s\" changed during execution\n"), fnbuf);
-                       ERR_pop_to_mark();
-                       return 0;
-               }
-#endif
-
-               if (PEM_read_bio_PrivateKey(bio, pkey, NULL, NULL) == NULL)
-               {
-                       char       *err = SSLerrmessage();
-
-                       printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("could not read private key file \"%s\": %s\n"),
-                                                         fnbuf, err);
-                       SSLerrfree(err);
-
-                       BIO_free(bio);
-                       ERR_pop_to_mark();
-                       return 0;
-               }
-
-               BIO_free(bio);
-       }
-
-       /* verify that the cert and key go together */
-       if (X509_check_private_key(*x509, *pkey) != 1)
-       {
-               char       *err = SSLerrmessage();
-
-               printfPQExpBuffer(&conn->errorMessage,
-                                                 libpq_gettext("certificate does not match private key file \"%s\": %s\n"),
-                                                 fnbuf, err);
-               SSLerrfree(err);
-               ERR_pop_to_mark();
-               return 0;
-       }
-
-       ERR_pop_to_mark();
-
-       return 1;
-}
-
 #ifdef ENABLE_THREAD_SAFETY
 /*
  *     Callback functions for OpenSSL internal locking
@@ -919,15 +659,20 @@ pq_lockingcallback(int mode, int n, const char *file, int line)
 #endif   /* ENABLE_THREAD_SAFETY */
 
 /*
- * Initialize SSL system. In threadsafe mode, this includes setting
- * up libcrypto callback functions to do thread locking.
+ * Initialize SSL system, in particular creating the SSL_context object
+ * that will be shared by all SSL-using connections in this process.
+ *
+ * In threadsafe mode, this includes setting up libcrypto callback functions
+ * to do thread locking.
  *
  * If the caller has told us (through PQinitOpenSSL) that he's taking care
  * of libcrypto, we expect that callbacks are already set, and won't try to
  * override it.
  *
  * The conn parameter is only used to be able to pass back an error
- * message - no connection local setup is made.
+ * message - no connection-local setup is made here.
+ *
+ * Returns 0 if OK, -1 on failure (with a message in conn->errorMessage).
  */
 static int
 init_ssl_system(PGconn *conn)
@@ -997,6 +742,7 @@ init_ssl_system(PGconn *conn)
                        SSL_library_init();
                        SSL_load_error_strings();
                }
+
                SSL_context = SSL_CTX_new(TLSv1_method());
                if (!SSL_context)
                {
@@ -1058,11 +804,19 @@ destroy_ssl_system(void)
 
        pthread_mutex_unlock(&ssl_config_mutex);
 #endif
-       return;
 }
 
 /*
- *     Initialize SSL context.
+ *     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.
+ *
+ *     Returns 0 if OK, -1 on failure (with a message in conn->errorMessage).
  */
 static int
 initialize_SSL(PGconn *conn)
@@ -1070,38 +824,240 @@ initialize_SSL(PGconn *conn)
        struct stat buf;
        char            homedir[MAXPGPATH];
        char            fnbuf[MAXPGPATH];
-
-       if (init_ssl_system(conn))
-               return -1;
+       char            sebuf[256];
+       bool            have_cert;
+       EVP_PKEY   *pkey = NULL;
 
        /*
-        * If sslmode is set to one of the verify options, perform certificate
-        * verification. If set to "verify-full" we will also do further
-        * verification after the connection has been completed.
-        *
-        * If we are going to look for either root certificate or CRL in the home
-        * directory, we need pqGetHomeDirectory() to succeed. In other cases, we
-        * don't need to get the home directory explicitly.
+        * We'll need the home directory if any of the relevant parameters are
+        * defaulted.
         */
-       if (!conn->sslrootcert || !conn->sslcrl)
+       if (!(conn->sslcert && strlen(conn->sslcert) > 0) ||
+               !(conn->sslkey && strlen(conn->sslkey) > 0) ||
+               !(conn->sslrootcert && strlen(conn->sslrootcert) > 0) ||
+               !(conn->sslcrl && strlen(conn->sslcrl) > 0))
        {
                if (!pqGetHomeDirectory(homedir, sizeof(homedir)))
                {
-                       if (conn->sslmode[0] == 'v')            /* "verify-ca" or
-                                                                                                * "verify-full" */
+                       printfPQExpBuffer(&conn->errorMessage,
+                                                         libpq_gettext("could not get home directory to locate client certificate files\n"));
+                       return -1;
+               }
+       }
+       else
+       {
+               homedir[0] = '\0';
+       }
+
+       /* Read the client certificate file */
+       if (conn->sslcert && strlen(conn->sslcert) > 0)
+               strncpy(fnbuf, conn->sslcert, sizeof(fnbuf));
+       else
+               snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_CERT_FILE);
+
+       if (stat(fnbuf, &buf) != 0)
+       {
+               /*
+                * If file is not present, just go on without a client cert; server
+                * might or might not accept the connection.  Any other error, however,
+                * is grounds for complaint.
+                */
+               if (errno != ENOENT)
+               {
+                       printfPQExpBuffer(&conn->errorMessage,
+                          libpq_gettext("could not open certificate file \"%s\": %s\n"),
+                                                         fnbuf, pqStrerror(errno, sebuf, sizeof(sebuf)));
+                       return -1;
+               }
+               have_cert = false;
+       }
+       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.
+                */
+               if (SSL_CTX_use_certificate_chain_file(SSL_context, fnbuf) != 1)
+               {
+                       char       *err = SSLerrmessage();
+
+                       printfPQExpBuffer(&conn->errorMessage,
+                          libpq_gettext("could not read certificate file \"%s\": %s\n"),
+                                                         fnbuf, err);
+                       SSLerrfree(err);
+                       return -1;
+               }
+               if (SSL_use_certificate_file(conn->ssl, fnbuf, SSL_FILETYPE_PEM) != 1)
+               {
+                       char       *err = SSLerrmessage();
+
+                       printfPQExpBuffer(&conn->errorMessage,
+                          libpq_gettext("could not read certificate file \"%s\": %s\n"),
+                                                         fnbuf, err);
+                       SSLerrfree(err);
+                       return -1;
+               }
+               /* need to load the associated private key, too */
+               have_cert = true;
+       }
+
+       /*
+        * Read the SSL key. If a key is specified, treat it as an engine:key
+        * combination if there is colon present - we don't support files with
+        * colon in the name. The exception is if the second character is a colon,
+        * in which case it can be a Windows filename with drive specification.
+        */
+       if (have_cert && conn->sslkey && strlen(conn->sslkey) > 0)
+       {
+#ifdef USE_SSL_ENGINE
+               if (strchr(conn->sslkey, ':')
+#ifdef WIN32
+                       && conn->sslkey[1] != ':'
+#endif
+                       )
+               {
+                       /* Colon, but not in second character, treat as engine:key */
+                       char       *engine_str = strdup(conn->sslkey);
+                       char       *engine_colon = strchr(engine_str, ':');
+
+                       *engine_colon = '\0';           /* engine_str now has engine name */
+                       engine_colon++;         /* engine_colon now has key name */
+
+                       conn->engine = ENGINE_by_id(engine_str);
+                       if (conn->engine == NULL)
+                       {
+                               char       *err = SSLerrmessage();
+
+                               printfPQExpBuffer(&conn->errorMessage,
+                                        libpq_gettext("could not load SSL engine \"%s\": %s\n"),
+                                                                 engine_str, err);
+                               SSLerrfree(err);
+                               free(engine_str);
+                               return -1;
+                       }
+
+                       if (ENGINE_init(conn->engine) == 0)
                        {
+                               char       *err = SSLerrmessage();
+
                                printfPQExpBuffer(&conn->errorMessage,
-                                                                 libpq_gettext("could not get home directory to locate root certificate file\n"));
+                               libpq_gettext("could not initialize SSL engine \"%s\": %s\n"),
+                                                                 engine_str, err);
+                               SSLerrfree(err);
+                               ENGINE_free(conn->engine);
+                               conn->engine = NULL;
+                               free(engine_str);
                                return -1;
                        }
+
+                       pkey = ENGINE_load_private_key(conn->engine, engine_colon,
+                                                                                  NULL, NULL);
+                       if (pkey == NULL)
+                       {
+                               char       *err = SSLerrmessage();
+
+                               printfPQExpBuffer(&conn->errorMessage,
+                                                                 libpq_gettext("could not read private SSL key \"%s\" from engine \"%s\": %s\n"),
+                                                                 engine_colon, engine_str, err);
+                               SSLerrfree(err);
+                               ENGINE_finish(conn->engine);
+                               ENGINE_free(conn->engine);
+                               conn->engine = NULL;
+                               free(engine_str);
+                               return -1;
+                       }
+                       if (SSL_use_PrivateKey(conn->ssl, pkey) != 1)
+                       {
+                               char       *err = SSLerrmessage();
+
+                               printfPQExpBuffer(&conn->errorMessage,
+                                                                 libpq_gettext("could not load private SSL key \"%s\" from engine \"%s\": %s\n"),
+                                                                 engine_colon, engine_str, err);
+                               SSLerrfree(err);
+                               ENGINE_finish(conn->engine);
+                               ENGINE_free(conn->engine);
+                               conn->engine = NULL;
+                               free(engine_str);
+                               return -1;
+                       }
+
+                       free(engine_str);
+
+                       fnbuf[0] = '\0';        /* indicate we're not going to load from a
+                                                                * file */
+               }
+               else
+#endif /* USE_SSL_ENGINE */
+               {
+                       /* PGSSLKEY is not an engine, treat it as a filename */
+                       strncpy(fnbuf, conn->sslkey, sizeof(fnbuf));
                }
        }
        else
        {
-               homedir[0] = '\0';
+               /* No PGSSLKEY specified, load default file */
+               snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_KEY_FILE);
        }
 
-       if (conn->sslrootcert)
+       if (have_cert && fnbuf[0] != '\0')
+       {
+               /* read the client key from file */
+
+               if (stat(fnbuf, &buf) != 0)
+               {
+                       printfPQExpBuffer(&conn->errorMessage,
+                                                         libpq_gettext("certificate present, but not private key file \"%s\"\n"),
+                                                         fnbuf);
+                       return -1;
+               }
+#ifndef WIN32
+               if (!S_ISREG(buf.st_mode) || buf.st_mode & (S_IRWXG | S_IRWXO))
+               {
+                       printfPQExpBuffer(&conn->errorMessage,
+                                                         libpq_gettext("private key file \"%s\" has group or world access; permissions should be u=rw (0600) or less\n"),
+                                                         fnbuf);
+                       return -1;
+               }
+#endif
+
+               if (SSL_use_PrivateKey_file(conn->ssl, fnbuf, SSL_FILETYPE_PEM) != 1)
+               {
+                       char       *err = SSLerrmessage();
+
+                       printfPQExpBuffer(&conn->errorMessage,
+                          libpq_gettext("could not load private key file \"%s\": %s\n"),
+                                                         fnbuf, err);
+                       SSLerrfree(err);
+                       return -1;
+               }
+       }
+
+       /* verify that the cert and key go together */
+       if (have_cert &&
+               SSL_check_private_key(conn->ssl) != 1)
+       {
+               char       *err = SSLerrmessage();
+
+               printfPQExpBuffer(&conn->errorMessage,
+                                                 libpq_gettext("certificate does not match private key file \"%s\": %s\n"),
+                                                 fnbuf, err);
+               SSLerrfree(err);
+               return -1;
+       }
+
+       /*
+        * 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)
                strncpy(fnbuf, conn->sslrootcert, sizeof(fnbuf));
        else
                snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, ROOT_CERT_FILE);
@@ -1123,20 +1079,19 @@ initialize_SSL(PGconn *conn)
 
                if ((cvstore = SSL_CTX_get_cert_store(SSL_context)) != NULL)
                {
-                       if (conn->sslcrl)
+                       if (conn->sslcrl && strlen(conn->sslcrl) > 0)
                                strncpy(fnbuf, conn->sslcrl, sizeof(fnbuf));
                        else
                                snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, ROOT_CRL_FILE);
 
-                       /* setting the flags to check against the complete CRL chain */
+                       /* Set the flags to check against the complete CRL chain */
                        if (X509_STORE_load_locations(cvstore, fnbuf, NULL) == 1)
-/* OpenSSL 0.96 does not support X509_V_FLAG_CRL_CHECK */
+                       {
+                               /* 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);
-                       /* if not found, silently ignore;  we do not require CRL */
 #else
-                       {
                                char       *err = SSLerrmessage();
 
                                printfPQExpBuffer(&conn->errorMessage,
@@ -1144,15 +1099,20 @@ initialize_SSL(PGconn *conn)
                                                                  fnbuf);
                                SSLerrfree(err);
                                return -1;
-                       }
 #endif
+                       }
+                       /* if not found, silently ignore;  we do not require CRL */
                }
 
-               SSL_CTX_set_verify(SSL_context, SSL_VERIFY_PEER, verify_cb);
+               SSL_set_verify(conn->ssl, SSL_VERIFY_PEER, verify_cb);
        }
        else
        {
-               /* stat() failed; assume cert file doesn't exist */
+               /*
+                * 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" */
                {
                        printfPQExpBuffer(&conn->errorMessage,
@@ -1162,9 +1122,6 @@ initialize_SSL(PGconn *conn)
                }
        }
 
-       /* set up mechanism to provide client certificate, if available */
-       SSL_CTX_set_client_cert_cb(SSL_context, client_cert_cb);
-
        return 0;
 }
 
@@ -1211,23 +1168,12 @@ open_client_SSL(PGconn *conn)
                                }
                        case SSL_ERROR_SSL:
                                {
-                                       /*
-                                        * If there are problems with the local certificate files,
-                                        * these will be detected by client_cert_cb() which is
-                                        * called from SSL_connect().  We want to return that
-                                        * error message and not the rather unhelpful error that
-                                        * OpenSSL itself returns.      So check to see if an error
-                                        * message was already stored.
-                                        */
-                                       if (conn->errorMessage.len == 0)
-                                       {
-                                               char       *err = SSLerrmessage();
+                                       char       *err = SSLerrmessage();
 
-                                               printfPQExpBuffer(&conn->errorMessage,
-                                                                                 libpq_gettext("SSL error: %s\n"),
-                                                                                 err);
-                                               SSLerrfree(err);
-                                       }
+                                       printfPQExpBuffer(&conn->errorMessage,
+                                                                         libpq_gettext("SSL error: %s\n"),
+                                                                         err);
+                                       SSLerrfree(err);
                                        close_SSL(conn);
                                        return PGRES_POLLING_FAILED;
                                }
@@ -1243,7 +1189,7 @@ open_client_SSL(PGconn *conn)
 
        /*
         * We already checked the server certificate in initialize_SSL() using
-        * SSL_CTX_set_verify() if root.crt exists.
+        * SSL_CTX_set_verify(), if root.crt exists.
         */
 
        /* pull out server distinguished and common names */