Add hints about protocol-version-related SSL connection failures.
authorTom Lane <tgl@sss.pgh.pa.us>
Sat, 27 Jun 2020 16:47:58 +0000 (12:47 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Sat, 27 Jun 2020 16:47:58 +0000 (12:47 -0400)
OpenSSL's native reports about problems related to protocol version
restrictions are pretty opaque and inconsistent.  When we get an
SSL error that is plausibly due to this, emit a hint message that
includes the range of SSL protocol versions we (think we) are
allowing.  This should at least get the user thinking in the right
direction to resolve the problem, even if the hint isn't totally
accurate, which it might not be for assorted reasons.

Back-patch to v13 where we increased the default minimum protocol
version, thereby increasing the risk of this class of failure.

Patch by me, reviewed by Daniel Gustafsson

Discussion: https://postgr.es/m/a9408304-4381-a5af-d259-e55d349ae4ce@2ndquadrant.com

src/backend/libpq/be-secure-openssl.c
src/include/common/openssl.h
src/interfaces/libpq/fe-secure-openssl.c

index 8adf64c78eeedf97e28a95f190eb57c8bf6ddd40..d1cf455ab442268e823f97387e6fea23024bbc49 100644 (file)
@@ -72,6 +72,7 @@ static bool dummy_ssl_passwd_cb_called = false;
 static bool ssl_is_server_start;
 
 static int ssl_protocol_version_to_openssl(int v);
+static const char *ssl_protocol_version_to_string(int v);
 
 /* ------------------------------------------------------------ */
 /*                      Public interface                       */
@@ -365,6 +366,7 @@ be_tls_open_server(Port *port)
    int         err;
    int         waitfor;
    unsigned long ecode;
+   bool        give_proto_hint;
 
    Assert(!port->ssl);
    Assert(!port->peer);
@@ -451,10 +453,50 @@ aloop:
                             errmsg("could not accept SSL connection: EOF detected")));
                break;
            case SSL_ERROR_SSL:
+               switch (ERR_GET_REASON(ecode))
+               {
+                       /*
+                        * UNSUPPORTED_PROTOCOL, WRONG_VERSION_NUMBER, and
+                        * TLSV1_ALERT_PROTOCOL_VERSION have been observed
+                        * when trying to communicate with an old OpenSSL
+                        * library, or when the client and server specify
+                        * disjoint protocol ranges.  NO_PROTOCOLS_AVAILABLE
+                        * occurs if there's a local misconfiguration (which
+                        * can happen despite our checks, if openssl.cnf
+                        * injects a limit we didn't account for).  It's not
+                        * very clear what would make OpenSSL return the other
+                        * codes listed here, but a hint about protocol
+                        * versions seems like it's appropriate for all.
+                        */
+                   case SSL_R_NO_PROTOCOLS_AVAILABLE:
+                   case SSL_R_UNSUPPORTED_PROTOCOL:
+                   case SSL_R_BAD_PROTOCOL_VERSION_NUMBER:
+                   case SSL_R_UNKNOWN_PROTOCOL:
+                   case SSL_R_UNKNOWN_SSL_VERSION:
+                   case SSL_R_UNSUPPORTED_SSL_VERSION:
+                   case SSL_R_VERSION_TOO_HIGH:
+                   case SSL_R_VERSION_TOO_LOW:
+                   case SSL_R_WRONG_SSL_VERSION:
+                   case SSL_R_WRONG_VERSION_NUMBER:
+                   case SSL_R_TLSV1_ALERT_PROTOCOL_VERSION:
+                       give_proto_hint = true;
+                       break;
+                   default:
+                       give_proto_hint = false;
+                       break;
+               }
                ereport(COMMERROR,
                        (errcode(ERRCODE_PROTOCOL_VIOLATION),
                         errmsg("could not accept SSL connection: %s",
-                               SSLerrmessage(ecode))));
+                               SSLerrmessage(ecode)),
+                        give_proto_hint ?
+                        errhint("This may indicate that the client does not support any SSL protocol version between %s and %s.",
+                                ssl_min_protocol_version ?
+                                ssl_protocol_version_to_string(ssl_min_protocol_version) :
+                                MIN_OPENSSL_TLS_VERSION,
+                                ssl_max_protocol_version ?
+                                ssl_protocol_version_to_string(ssl_max_protocol_version) :
+                                MAX_OPENSSL_TLS_VERSION) : 0));
                break;
            case SSL_ERROR_ZERO_RETURN:
                ereport(COMMERROR,
@@ -1328,6 +1370,29 @@ ssl_protocol_version_to_openssl(int v)
    return -1;
 }
 
+/*
+ * Likewise provide a mapping to strings.
+ */
+static const char *
+ssl_protocol_version_to_string(int v)
+{
+   switch (v)
+   {
+       case PG_TLS_ANY:
+           return "any";
+       case PG_TLS1_VERSION:
+           return "TLSv1";
+       case PG_TLS1_1_VERSION:
+           return "TLSv1.1";
+       case PG_TLS1_2_VERSION:
+           return "TLSv1.2";
+       case PG_TLS1_3_VERSION:
+           return "TLSv1.3";
+   }
+
+   return "(unrecognized)";
+}
+
 
 static void
 default_openssl_tls_init(SSL_CTX *context, bool isServerStart)
index 47fa1299945c574bd09d26908c4019eb179152af..9d1c681d9f845d58b710102eeacd2964fb3db602 100644 (file)
 #ifdef USE_OPENSSL
 #include <openssl/ssl.h>
 
+/*
+ * OpenSSL doesn't provide any very nice way to identify the min/max
+ * protocol versions the library supports, so we fake it as best we can.
+ * Note in particular that this doesn't account for restrictions that
+ * might be specified in the installation's openssl.cnf.
+ *
+ * We disable SSLv3 and older in library setup, so TLSv1 is the oldest
+ * protocol version of interest.
+ */
+#define MIN_OPENSSL_TLS_VERSION  "TLSv1"
+
+#if defined(TLS1_3_VERSION)
+#define MAX_OPENSSL_TLS_VERSION  "TLSv1.3"
+#elif defined(TLS1_2_VERSION)
+#define MAX_OPENSSL_TLS_VERSION  "TLSv1.2"
+#elif defined(TLS1_1_VERSION)
+#define MAX_OPENSSL_TLS_VERSION  "TLSv1.1"
+#else
+#define MAX_OPENSSL_TLS_VERSION  "TLSv1"
+#endif
+
 /* src/common/protocol_openssl.c */
 #ifndef SSL_CTX_set_min_proto_version
 extern int SSL_CTX_set_min_proto_version(SSL_CTX *ctx, int version);
 extern int SSL_CTX_set_max_proto_version(SSL_CTX *ctx, int version);
 #endif
 
-#endif
+#endif                         /* USE_OPENSSL */
 
 #endif                         /* COMMON_OPENSSL_H */
index 2d813ef5f9beda70cfb824e4f3cfe0cb172e4f54..b5b2006b75ded8a1e406d0127bebaa5720f5c2f6 100644 (file)
@@ -1304,6 +1304,45 @@ open_client_SSL(PGconn *conn)
                                      libpq_gettext("SSL error: %s\n"),
                                      err);
                    SSLerrfree(err);
+                   switch (ERR_GET_REASON(ecode))
+                   {
+                           /*
+                            * UNSUPPORTED_PROTOCOL, WRONG_VERSION_NUMBER, and
+                            * TLSV1_ALERT_PROTOCOL_VERSION have been observed
+                            * when trying to communicate with an old OpenSSL
+                            * library, or when the client and server specify
+                            * disjoint protocol ranges.
+                            * NO_PROTOCOLS_AVAILABLE occurs if there's a
+                            * local misconfiguration (which can happen
+                            * despite our checks, if openssl.cnf injects a
+                            * limit we didn't account for).  It's not very
+                            * clear what would make OpenSSL return the other
+                            * codes listed here, but a hint about protocol
+                            * versions seems like it's appropriate for all.
+                            */
+                       case SSL_R_NO_PROTOCOLS_AVAILABLE:
+                       case SSL_R_UNSUPPORTED_PROTOCOL:
+                       case SSL_R_BAD_PROTOCOL_VERSION_NUMBER:
+                       case SSL_R_UNKNOWN_PROTOCOL:
+                       case SSL_R_UNKNOWN_SSL_VERSION:
+                       case SSL_R_UNSUPPORTED_SSL_VERSION:
+                       case SSL_R_VERSION_TOO_HIGH:
+                       case SSL_R_VERSION_TOO_LOW:
+                       case SSL_R_WRONG_SSL_VERSION:
+                       case SSL_R_WRONG_VERSION_NUMBER:
+                       case SSL_R_TLSV1_ALERT_PROTOCOL_VERSION:
+                           appendPQExpBuffer(&conn->errorMessage,
+                                             libpq_gettext("This may indicate that the server does not support any SSL protocol version between %s and %s.\n"),
+                                             conn->ssl_min_protocol_version ?
+                                             conn->ssl_min_protocol_version :
+                                             MIN_OPENSSL_TLS_VERSION,
+                                             conn->ssl_max_protocol_version ?
+                                             conn->ssl_max_protocol_version :
+                                             MAX_OPENSSL_TLS_VERSION);
+                           break;
+                       default:
+                           break;
+                   }
                    pgtls_close(conn);
                    return PGRES_POLLING_FAILED;
                }