diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c index 8adf64c78e..d1cf455ab4 100644 --- a/src/backend/libpq/be-secure-openssl.c +++ b/src/backend/libpq/be-secure-openssl.c @@ -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) diff --git a/src/include/common/openssl.h b/src/include/common/openssl.h index 47fa129994..9d1c681d9f 100644 --- a/src/include/common/openssl.h +++ b/src/include/common/openssl.h @@ -17,12 +17,33 @@ #ifdef USE_OPENSSL #include +/* + * 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 */ diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c index 2d813ef5f9..b5b2006b75 100644 --- a/src/interfaces/libpq/fe-secure-openssl.c +++ b/src/interfaces/libpq/fe-secure-openssl.c @@ -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; }