[HACKERS] [Patch] Log SSL certificate verification errors - Mailing list pgsql-hackers

From Graham Leggett
Subject [HACKERS] [Patch] Log SSL certificate verification errors
Date
Msg-id 2DAFAF7F-012D-4042-8C88-0A7016F9D93A@sharp.fm
Whole thread Raw
Responses Re: [HACKERS] [Patch] Log SSL certificate verification errors  (Michael Paquier <michael.paquier@gmail.com>)
Re: [HACKERS] [Patch] Log SSL certificate verification errors  (Laurenz Albe <laurenz.albe@cybertec.at>)
List pgsql-hackers
Hi all,

Currently neither the server side nor the client side SSL certificate verify callback does anything, leading to
potentialhair-tearing-out moments. 

The following patch to master implements logging of all certificate verification failures, as well as (crucially) which
certificatesfailed to verify, and at what depth, so the admin can zoom in straight onto the problem without any
guessing.

The SSL test suite runs without regression:

Little-Net:ssl minfrin$ make check
rm -rf '/Users/minfrin/src/postgres/postgres-trunk'/tmp_install
/bin/sh ../../../config/install-sh -c -d '/Users/minfrin/src/postgres/postgres-trunk'/tmp_install/log
/Applications/Xcode.app/Contents/Developer/usr/bin/make -C '../../..'
DESTDIR='/Users/minfrin/src/postgres/postgres-trunk'/tmp_installinstall
>'/Users/minfrin/src/postgres/postgres-trunk'/tmp_install/log/install.log2>&1 
rm -rf '/Users/minfrin/src/postgres/postgres-trunk/src/test/ssl'/tmp_check
/bin/sh ../../../config/install-sh -c -d '/Users/minfrin/src/postgres/postgres-trunk/src/test/ssl'/tmp_check
cd . && TESTDIR='/Users/minfrin/src/postgres/postgres-trunk/src/test/ssl'
PATH="/Users/minfrin/src/postgres/postgres-trunk/tmp_install/tmp/postgresql/bin:$PATH"
DYLD_LIBRARY_PATH="/Users/minfrin/src/postgres/postgres-trunk/tmp_install/tmp/postgresql/lib"PGPORT='65432'
PG_REGRESS='/Users/minfrin/src/postgres/postgres-trunk/src/test/ssl/../../../src/test/regress/pg_regress'
/opt/local/bin/prove-I ../../../src/test/perl/ -I .  t/*.pl 
t/001_ssltests.pl .. ok
All tests successful.
Files=1, Tests=40,  6 wallclock secs ( 0.04 usr  0.01 sys +  2.01 cusr  1.21 csys =  3.27 CPU)
Result: PASS


Index: src/backend/libpq/be-secure-openssl.c
===================================================================
--- src/backend/libpq/be-secure-openssl.c    (revision 61109)
+++ src/backend/libpq/be-secure-openssl.c    (working copy)
@@ -999,9 +999,9 @@/* *    Certificate verification callback *
- *    This callback allows us to log intermediate problems during
- *    verification, but for now we'll see if the final error message
- *    contains enough information.
+ *    There are 50 ways to leave your lover, and 67 ways to fail
+ *    certificate verification. Log details of all failed certificate
+ *    verification results. * *    This callback also allows us to override the default acceptance *    criteria
(e.g.,accepting self-signed or expired certs), but 
@@ -1010,6 +1010,28 @@static intverify_cb(int ok, X509_STORE_CTX *ctx){
+    char *subject, *issuer;
+    X509 *cert;
+    int err, depth;
+
+    if (!ok)
+    {
+        cert = X509_STORE_CTX_get_current_cert(ctx);
+        err = X509_STORE_CTX_get_error(ctx);
+        depth = X509_STORE_CTX_get_error_depth(ctx);
+
+        subject = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0);
+        issuer = X509_NAME_oneline(X509_get_issuer_name(cert), NULL, 0);
+
+        ereport(COMMERROR,
+            (errcode(ERRCODE_PROTOCOL_VIOLATION),
+                errmsg("SSL certificate verification result: %s (depth %d, subject '%s', issuer '%s')",
+                    X509_verify_cert_error_string(err), depth, subject, issuer)));
+
+        OPENSSL_free(subject);
+        OPENSSL_free(issuer);
+    }
+    return ok;}
Index: src/interfaces/libpq/fe-secure-openssl.c
===================================================================
--- src/interfaces/libpq/fe-secure-openssl.c    (revision 61109)
+++ src/interfaces/libpq/fe-secure-openssl.c    (working copy)
@@ -82,6 +82,8 @@static bool ssl_lib_initialized = false;
+static int ssl_ex_data_index;
+#ifdef ENABLE_THREAD_SAFETYstatic long ssl_open_connections = 0;
@@ -182,6 +184,7 @@     */    SOCK_ERRNO_SET(0);    ERR_clear_error();
+    resetPQExpBuffer(&conn->errorMessage);    n = SSL_read(conn->ssl, ptr, len);    err = SSL_get_error(conn->ssl, n);
@@ -200,7 +203,7 @@            if (n < 0)            {                /* Not supposed to happen, so we don't translate
themsg */ 
-                printfPQExpBuffer(&conn->errorMessage,
+                appendPQExpBuffer(&conn->errorMessage,                                  "SSL_read failed but did not
provideerror information\n");                /* assume the connection is broken */                result_errno =
ECONNRESET;
@@ -224,13 +227,13 @@                result_errno = SOCK_ERRNO;                if (result_errno == EPIPE ||
      result_errno == ECONNRESET) 
-                    printfPQExpBuffer(&conn->errorMessage,
+                    appendPQExpBuffer(&conn->errorMessage,                                      libpq_gettext(
                                          "server closed the connection unexpectedly\n"
                  "\tThis probably means the server terminated abnormally\n"
       "\tbefore or while processing the request.\n"));                else 
-                    printfPQExpBuffer(&conn->errorMessage,
+                    appendPQExpBuffer(&conn->errorMessage,                                      libpq_gettext("SSL
SYSCALLerror: %s\n"),                                      SOCK_STRERROR(result_errno,
                 sebuf, sizeof(sebuf))); 
@@ -237,7 +240,7 @@            }            else            {
-                printfPQExpBuffer(&conn->errorMessage,
+                appendPQExpBuffer(&conn->errorMessage,                                  libpq_gettext("SSL SYSCALL
error:EOF detected\n"));                /* assume the connection is broken */                result_errno = ECONNRESET; 
@@ -248,7 +251,7 @@            {                char       *errm = SSLerrmessage(ecode);
-                printfPQExpBuffer(&conn->errorMessage,
+                appendPQExpBuffer(&conn->errorMessage,                                  libpq_gettext("SSL error:
%s\n"),errm);                SSLerrfree(errm);                /* assume the connection is broken */ 
@@ -263,13 +266,13 @@             * a clean connection closure, so we should not report it as a             * server
crash.            */ 
-            printfPQExpBuffer(&conn->errorMessage,
+            appendPQExpBuffer(&conn->errorMessage,                              libpq_gettext("SSL connection has been
closedunexpectedly\n"));            result_errno = ECONNRESET;            n = -1;            break;        default: 
-            printfPQExpBuffer(&conn->errorMessage,
+            appendPQExpBuffer(&conn->errorMessage,                              libpq_gettext("unrecognized SSL error
code:%d\n"),                              err);            /* assume the connection is broken */ 
@@ -302,6 +305,7 @@    SOCK_ERRNO_SET(0);    ERR_clear_error();
+    resetPQExpBuffer(&conn->errorMessage);    n = SSL_write(conn->ssl, ptr, len);    err = SSL_get_error(conn->ssl,
n);   ecode = (err != SSL_ERROR_NONE || n < 0) ? ERR_get_error() : 0; 
@@ -311,7 +315,7 @@            if (n < 0)            {                /* Not supposed to happen, so we don't translate
themsg */ 
-                printfPQExpBuffer(&conn->errorMessage,
+                appendPQExpBuffer(&conn->errorMessage,                                  "SSL_write failed but did not
provideerror information\n");                /* assume the connection is broken */                result_errno =
ECONNRESET;
@@ -333,13 +337,13 @@            {                result_errno = SOCK_ERRNO;                if (result_errno == EPIPE
||result_errno == ECONNRESET) 
-                    printfPQExpBuffer(&conn->errorMessage,
+                    appendPQExpBuffer(&conn->errorMessage,                                      libpq_gettext(
                                          "server closed the connection unexpectedly\n"
                  "\tThis probably means the server terminated abnormally\n"
       "\tbefore or while processing the request.\n"));                else 
-                    printfPQExpBuffer(&conn->errorMessage,
+                    appendPQExpBuffer(&conn->errorMessage,                                      libpq_gettext("SSL
SYSCALLerror: %s\n"),                                      SOCK_STRERROR(result_errno,
                 sebuf, sizeof(sebuf))); 
@@ -346,7 +350,7 @@            }            else            {
-                printfPQExpBuffer(&conn->errorMessage,
+                appendPQExpBuffer(&conn->errorMessage,                                  libpq_gettext("SSL SYSCALL
error:EOF detected\n"));                /* assume the connection is broken */                result_errno = ECONNRESET; 
@@ -357,7 +361,7 @@            {                char       *errm = SSLerrmessage(ecode);
-                printfPQExpBuffer(&conn->errorMessage,
+                appendPQExpBuffer(&conn->errorMessage,                                  libpq_gettext("SSL error:
%s\n"),errm);                SSLerrfree(errm);                /* assume the connection is broken */ 
@@ -372,13 +376,13 @@             * a clean connection closure, so we should not report it as a             * server
crash.            */ 
-            printfPQExpBuffer(&conn->errorMessage,
+            appendPQExpBuffer(&conn->errorMessage,                              libpq_gettext("SSL connection has been
closedunexpectedly\n"));            result_errno = ECONNRESET;            n = -1;            break;        default: 
-            printfPQExpBuffer(&conn->errorMessage,
+            appendPQExpBuffer(&conn->errorMessage,                              libpq_gettext("unrecognized SSL error
code:%d\n"),                              err);            /* assume the connection is broken */ 
@@ -400,9 +404,9 @@/* *    Certificate verification callback *
- *    This callback allows us to log intermediate problems during
- *    verification, but there doesn't seem to be a clean way to get
- *    our PGconn * structure.  So we can't log anything!
+ *    There are 50 ways to leave your lover, and 67 ways to fail
+ *    certificate verification. Return details of all failed certificate
+ *    verification results. * *    This callback also allows us to override the default acceptance *    criteria
(e.g.,accepting self-signed or expired certs), but 
@@ -411,6 +415,32 @@static intverify_cb(int ok, X509_STORE_CTX *ctx){
+    char *subject, *issuer;
+    X509 *cert;
+    SSL *ssl;
+    PGconn *conn;
+    int err, depth;
+
+    if (!ok)
+    {
+        cert = X509_STORE_CTX_get_current_cert(ctx);
+        err = X509_STORE_CTX_get_error(ctx);
+        depth = X509_STORE_CTX_get_error_depth(ctx);
+
+        subject = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0);
+        issuer = X509_NAME_oneline(X509_get_issuer_name(cert), NULL, 0);
+
+        ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
+        conn = SSL_get_ex_data(ssl, ssl_ex_data_index);
+
+        appendPQExpBuffer(&conn->errorMessage,
+            libpq_gettext("SSL certificate verification result: %s (depth %d, subject '%s', issuer '%s')\n"),
+                X509_verify_cert_error_string(err), depth, subject, issuer);
+
+        OPENSSL_free(subject);
+        OPENSSL_free(issuer);
+    }
+    return ok;}
@@ -490,7 +520,7 @@    /* Should not happen... */    if (name_entry == NULL)    {
-        printfPQExpBuffer(&conn->errorMessage,
+        appendPQExpBuffer(&conn->errorMessage,                          libpq_gettext("SSL certificate's name entry is
missing\n"));       return -1;    } 
@@ -510,7 +540,7 @@    name = malloc(len + 1);    if (name == NULL)    {
-        printfPQExpBuffer(&conn->errorMessage,
+        appendPQExpBuffer(&conn->errorMessage,                          libpq_gettext("out of memory\n"));
return-1;    } 
@@ -524,7 +554,7 @@    if (len != strlen(name))    {        free(name);
-        printfPQExpBuffer(&conn->errorMessage,
+        appendPQExpBuffer(&conn->errorMessage,                          libpq_gettext("SSL certificate's name contains
embeddednull\n"));        return -1;    } 
@@ -576,7 +606,7 @@    /* Check that we have a hostname to compare with. */    if (!(host && host[0] != '\0'))    {
-        printfPQExpBuffer(&conn->errorMessage,
+        appendPQExpBuffer(&conn->errorMessage,                          libpq_gettext("host name must be specified for
averified SSL connection\n"));        return false;    } 
@@ -668,7 +698,7 @@         */        if (names_examined > 1)        {
-            printfPQExpBuffer(&conn->errorMessage,
+            appendPQExpBuffer(&conn->errorMessage,                              libpq_ngettext("server certificate for
\"%s\"(and %d other name) does not match host name \"%s\"\n",                                             "server
certificatefor \"%s\" (and %d other names) does not match host name \"%s\"\n",
  names_examined - 1), 
@@ -676,13 +706,13 @@        }        else if (names_examined == 1)        {
-            printfPQExpBuffer(&conn->errorMessage,
+            appendPQExpBuffer(&conn->errorMessage,                              libpq_gettext("server certificate for
\"%s\"does not match host name \"%s\"\n"),                              first_name, host);        }        else
{
-            printfPQExpBuffer(&conn->errorMessage,
+            appendPQExpBuffer(&conn->errorMessage,                              libpq_gettext("could not get server's
hostname from server certificate\n"));        }    } 
@@ -823,6 +853,8 @@            SSL_library_init();            SSL_load_error_strings();#endif
+            ssl_ex_data_index = SSL_get_ex_new_index(0,
+                "postgresql index", NULL, NULL, NULL);        }        ssl_lib_initialized = true;    }
@@ -899,6 +931,8 @@    bool        have_rootcert;    EVP_PKEY   *pkey = NULL;
+    resetPQExpBuffer(&conn->errorMessage);
+    /*     * We'll need the home directory if any of the relevant parameters are     * defaulted.  If
pqGetHomeDirectoryfails, act as though none of the 
@@ -924,7 +958,7 @@    {        char       *err = SSLerrmessage(ERR_get_error());
-        printfPQExpBuffer(&conn->errorMessage,
+        appendPQExpBuffer(&conn->errorMessage,                          libpq_gettext("could not create SSL context:
%s\n"),                         err);        SSLerrfree(err); 
@@ -961,7 +995,7 @@        {            char       *err = SSLerrmessage(ERR_get_error());
-            printfPQExpBuffer(&conn->errorMessage,
+            appendPQExpBuffer(&conn->errorMessage,                              libpq_gettext("could not read root
certificatefile \"%s\": %s\n"),                              fnbuf, err);            SSLerrfree(err); 
@@ -989,7 +1023,7 @@#else                char       *err = SSLerrmessage(ERR_get_error());
-                printfPQExpBuffer(&conn->errorMessage,
+                appendPQExpBuffer(&conn->errorMessage,                                  libpq_gettext("SSL library
doesnot support CRL certificates (file \"%s\")\n"),                                  fnbuf);
SSLerrfree(err);
@@ -1017,11 +1051,11 @@             * that it seems worth having a specialized error message for it.             */
      if (fnbuf[0] == '\0') 
-                printfPQExpBuffer(&conn->errorMessage,
+                appendPQExpBuffer(&conn->errorMessage,                                  libpq_gettext("could not get
homedirectory to locate root certificate file\n"                                                "Either provide the
fileor change sslmode to disable server certificate verification.\n"));            else 
-                printfPQExpBuffer(&conn->errorMessage,
+                appendPQExpBuffer(&conn->errorMessage,                                  libpq_gettext("root
certificatefile \"%s\" does not exist\n"                                                "Either provide the file or
changesslmode to disable server certificate verification.\n"), fnbuf);            SSL_CTX_free(SSL_context); 
@@ -1052,7 +1086,7 @@         */        if (errno != ENOENT && errno != ENOTDIR)        {
-            printfPQExpBuffer(&conn->errorMessage,
+            appendPQExpBuffer(&conn->errorMessage,                              libpq_gettext("could not open
certificatefile \"%s\": %s\n"),                              fnbuf, pqStrerror(errno, sebuf, sizeof(sebuf)));
SSL_CTX_free(SSL_context); 
@@ -1071,7 +1105,7 @@        {            char       *err = SSLerrmessage(ERR_get_error());
-            printfPQExpBuffer(&conn->errorMessage,
+            appendPQExpBuffer(&conn->errorMessage,                              libpq_gettext("could not read
certificatefile \"%s\": %s\n"),                              fnbuf, err);            SSLerrfree(err); 
@@ -1096,7 +1130,7 @@    {        char       *err = SSLerrmessage(ERR_get_error());
-        printfPQExpBuffer(&conn->errorMessage,
+        appendPQExpBuffer(&conn->errorMessage,                          libpq_gettext("could not establish SSL
connection:%s\n"),                          err);        SSLerrfree(err); 
@@ -1134,7 +1168,7 @@            if (engine_str == NULL)            {
-                printfPQExpBuffer(&conn->errorMessage,
+                appendPQExpBuffer(&conn->errorMessage,                                  libpq_gettext("out of
memory\n"));               return -1;            } 
@@ -1150,7 +1184,7 @@            {                char       *err = SSLerrmessage(ERR_get_error());
-                printfPQExpBuffer(&conn->errorMessage,
+                appendPQExpBuffer(&conn->errorMessage,                                  libpq_gettext("could not load
SSLengine \"%s\": %s\n"),                                  engine_str, err);                SSLerrfree(err); 
@@ -1162,7 +1196,7 @@            {                char       *err = SSLerrmessage(ERR_get_error());
-                printfPQExpBuffer(&conn->errorMessage,
+                appendPQExpBuffer(&conn->errorMessage,                                  libpq_gettext("could not
initializeSSL engine \"%s\": %s\n"),                                  engine_str, err);                SSLerrfree(err); 
@@ -1178,7 +1212,7 @@            {                char       *err = SSLerrmessage(ERR_get_error());
-                printfPQExpBuffer(&conn->errorMessage,
+                appendPQExpBuffer(&conn->errorMessage,                                  libpq_gettext("could not read
privateSSL key \"%s\" from engine \"%s\": %s\n"),                                  engine_colon, engine_str, err);
         SSLerrfree(err); 
@@ -1192,7 +1226,7 @@            {                char       *err = SSLerrmessage(ERR_get_error());
-                printfPQExpBuffer(&conn->errorMessage,
+                appendPQExpBuffer(&conn->errorMessage,                                  libpq_gettext("could not load
privateSSL key \"%s\" from engine \"%s\": %s\n"),                                  engine_colon, engine_str, err);
         SSLerrfree(err); 
@@ -1229,7 +1263,7 @@        if (stat(fnbuf, &buf) != 0)        {
-            printfPQExpBuffer(&conn->errorMessage,
+            appendPQExpBuffer(&conn->errorMessage,                              libpq_gettext("certificate present,
butnot private key file \"%s\"\n"),                              fnbuf);            return -1; 
@@ -1237,7 +1271,7 @@#ifndef WIN32        if (!S_ISREG(buf.st_mode) || buf.st_mode & (S_IRWXG | S_IRWXO))        {
-            printfPQExpBuffer(&conn->errorMessage,
+            appendPQExpBuffer(&conn->errorMessage,                              libpq_gettext("private key file \"%s\"
hasgroup or world access; permissions should be u=rw (0600) or less\n"),                              fnbuf);
return -1; 
@@ -1248,7 +1282,7 @@        {            char       *err = SSLerrmessage(ERR_get_error());
-            printfPQExpBuffer(&conn->errorMessage,
+            appendPQExpBuffer(&conn->errorMessage,                              libpq_gettext("could not load private
keyfile \"%s\": %s\n"),                              fnbuf, err);            SSLerrfree(err); 
@@ -1262,7 +1296,7 @@    {        char       *err = SSLerrmessage(ERR_get_error());
-        printfPQExpBuffer(&conn->errorMessage,
+        appendPQExpBuffer(&conn->errorMessage,                          libpq_gettext("certificate does not match
privatekey file \"%s\": %s\n"),                          fnbuf, err);        SSLerrfree(err); 
@@ -1274,7 +1308,10 @@     * callback.     */    if (have_rootcert)
+    {        SSL_set_verify(conn->ssl, SSL_VERIFY_PEER, verify_cb);
+        SSL_set_ex_data(conn->ssl, ssl_ex_data_index, conn);
+    }    /*     * If the OpenSSL version used supports it (from 1.0.0 on) and the user
@@ -1299,6 +1336,7 @@    int            r;    ERR_clear_error();
+    resetPQExpBuffer(&conn->errorMessage);    r = SSL_connect(conn->ssl);    if (r <= 0)    {
@@ -1319,11 +1357,11 @@                    char        sebuf[256];                    if (r == -1)
-                        printfPQExpBuffer(&conn->errorMessage,
+                        appendPQExpBuffer(&conn->errorMessage,
libpq_gettext("SSLSYSCALL error: %s\n"),                                          SOCK_STRERROR(SOCK_ERRNO, sebuf,
sizeof(sebuf)));                   else 
-                        printfPQExpBuffer(&conn->errorMessage,
+                        appendPQExpBuffer(&conn->errorMessage,
libpq_gettext("SSLSYSCALL error: EOF detected\n"));                    pgtls_close(conn);                    return
PGRES_POLLING_FAILED;
@@ -1332,7 +1370,7 @@                {                    char       *err = SSLerrmessage(ecode);
-                    printfPQExpBuffer(&conn->errorMessage,
+                    appendPQExpBuffer(&conn->errorMessage,                                      libpq_gettext("SSL
error:%s\n"),                                      err);                    SSLerrfree(err); 
@@ -1341,7 +1379,7 @@                }            default:
-                printfPQExpBuffer(&conn->errorMessage,
+                appendPQExpBuffer(&conn->errorMessage,                                  libpq_gettext("unrecognized
SSLerror code: %d\n"),                                  err);                pgtls_close(conn); 
@@ -1362,7 +1400,7 @@        err = SSLerrmessage(ERR_get_error());
-        printfPQExpBuffer(&conn->errorMessage,
+        appendPQExpBuffer(&conn->errorMessage,                          libpq_gettext("certificate could not be
obtained:%s\n"),                          err);        SSLerrfree(err); 


Regards,
Graham
—



pgsql-hackers by date:

Previous
From: Peter Eisentraut
Date:
Subject: Re: [HACKERS] Add some const decorations to prototypes
Next
From: Robert Haas
Date:
Subject: Re: [HACKERS] parallelize queries containing initplans