From 169328694b20ae836e2277cd7b9c7f6dce03fb94 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 29 May 2018 16:25:14 -0400
Subject: [PATCH] Allow tls-server-end-point with OpenSSL 1.0.0 and 1.0.1

Since OpenSSL 1.0.2, the contents of X509 are shadowed and only
accessible using dedicated upstream APIs.  In order to get the
certificate hash, X509_get_signature_nid is a central part of it.

However, it happens that getting the hashing algorithm for the
certificate is actually possible by looking at the contents of
X509->sig_alg->algorithm.  Another part of the puzzle is
OBJ_find_sigid_algs, which has been introduced in OpenSSL 1.0.0, and is
the reliable way the base signature algorithm from upstream structures.

Down to OpenSSL 0.9.8, we could try to rely on EVP_get_digestbynid
to get this data however seeing this work is rather surprising if one
looks at the upstream commit ee1d9ec0 which re-established the links
between signature algorithms and digests using a hack, so it looks like
a bad bet for the future to rely on this hack to be supported in future
versions of OpenSSL.  Hence it is not considered and only OpenSSL 0.9.8
does not get support for tls-server-end-point which is the oldest
version supported on HEAD.

Another thing to note is that OBJ_find_sigid_alg can crash if a NULL
input is provided as second or third argument, which is an issue fixed
by upstream in 177f27d, included in 1.0.1 and above but *not* 1.0.0.

Patch by Michael Paquier and Steven Fackler, which is based on an
off-list discussion between the two.
---
 configure.in                             |  2 +-
 src/backend/libpq/be-secure-openssl.c    | 21 ++++++++++++++++-----
 src/include/pg_config.h.in               |  3 +++
 src/interfaces/libpq/fe-secure-openssl.c | 21 ++++++++++++++++-----
 src/test/ssl/t/002_scram.pl              |  2 +-
 5 files changed, 37 insertions(+), 12 deletions(-)

diff --git a/configure.in b/configure.in
index 862d8b128d..0f3a352f20 100644
--- a/configure.in
+++ b/configure.in
@@ -1160,7 +1160,7 @@ if test "$with_openssl" = yes ; then
      AC_SEARCH_LIBS(CRYPTO_new_ex_data, [eay32 crypto], [], [AC_MSG_ERROR([library 'eay32' or 'crypto' is required for OpenSSL])])
      AC_SEARCH_LIBS(SSL_new, [ssleay32 ssl], [], [AC_MSG_ERROR([library 'ssleay32' or 'ssl' is required for OpenSSL])])
   fi
-  AC_CHECK_FUNCS([SSL_clear_options SSL_get_current_compression X509_get_signature_nid])
+  AC_CHECK_FUNCS([SSL_clear_options SSL_get_current_compression X509_get_signature_nid OBJ_find_sigid_algs])
   # Functions introduced in OpenSSL 1.1.0. We used to check for
   # OPENSSL_VERSION_NUMBER, but that didn't work with 1.1.0, because LibreSSL
   # defines OPENSSL_VERSION_NUMBER to claim version 2.0.0, even though it
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 48b468f62f..5b03558613 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -1126,13 +1126,15 @@ be_tls_get_peer_finished(Port *port, size_t *len)
 char *
 be_tls_get_certificate_hash(Port *port, size_t *len)
 {
-#ifdef HAVE_X509_GET_SIGNATURE_NID
+#ifdef HAVE_OBJ_FIND_SIGID_ALGS
 	X509	   *server_cert;
 	char	   *cert_hash;
 	const EVP_MD *algo_type = NULL;
 	unsigned char hash[EVP_MAX_MD_SIZE];	/* size for SHA-512 */
 	unsigned int hash_size;
 	int			algo_nid;
+	int			key_nid;
+	int			digest_alg;
 
 	*len = 0;
 	server_cert = SSL_get_certificate(port->ssl);
@@ -1140,11 +1142,20 @@ be_tls_get_certificate_hash(Port *port, size_t *len)
 		return NULL;
 
 	/*
-	 * Get the signature algorithm of the certificate to determine the hash
-	 * algorithm to use for the result.
+	 * Get the signature algorithm of the certificate to determine the
+	 * hash algorithm to use for the result.  X509_get_signature_nid has
+	 * been introduced in 1.0.2.  Down to OpenSSL 0.9.8 the signature
+	 * algorithm is available directly through X509, however
+	 * OBJ_find_sigid_alg(), which is the stable way of getting the hash
+	 * algorithm has been only introduced in 1.0.0.
 	 */
-	if (!OBJ_find_sigid_algs(X509_get_signature_nid(server_cert),
-							 &algo_nid, NULL))
+#ifdef HAVE_X509_GET_SIGNATURE_NID
+	digest_alg = X509_get_signature_nid(server_cert);
+#else
+	digest_alg = OBJ_obj2nid(server_cert->sig_alg->algorithm);
+#endif
+
+	if (!OBJ_find_sigid_algs(digest_alg, &algo_nid, &key_nid))
 		elog(ERROR, "could not determine server certificate signature algorithm");
 
 	/*
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 89b8804251..733617a6c1 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -409,6 +409,9 @@
 /* Define to 1 if you have the <net/if.h> header file. */
 #undef HAVE_NET_IF_H
 
+/* Define to 1 if you have the `OBJ_find_sigid_algs' function. */
+#undef HAVE_OBJ_FIND_SIGID_ALGS
+
 /* Define to 1 if you have the `OPENSSL_init_ssl' function. */
 #undef HAVE_OPENSSL_INIT_SSL
 
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 43640e3799..e4eeeef2e5 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -392,13 +392,15 @@ pgtls_get_finished(PGconn *conn, size_t *len)
 char *
 pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
 {
-#ifdef HAVE_X509_GET_SIGNATURE_NID
+#ifdef HAVE_OBJ_FIND_SIGID_ALGS
 	X509	   *peer_cert;
 	const EVP_MD *algo_type;
 	unsigned char hash[EVP_MAX_MD_SIZE];	/* size for SHA-512 */
 	unsigned int hash_size;
 	int			algo_nid;
+	int			key_nid;
 	char	   *cert_hash;
+	int			digest_alg;
 
 	*len = 0;
 
@@ -408,11 +410,20 @@ pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
 	peer_cert = conn->peer;
 
 	/*
-	 * Get the signature algorithm of the certificate to determine the hash
-	 * algorithm to use for the result.
+	 * Get the signature algorithm of the certificate to determine the
+	 * hash algorithm to use for the result.  X509_get_signature_nid has
+	 * been introduced in 1.0.2.  Down to OpenSSL 0.9.8 the signature
+	 * algorithm is available directly through X509, however
+	 * OBJ_find_sigid_alg(), which is the stable way of getting the hash
+	 * algorithm has been only introduced in 1.0.0.
 	 */
-	if (!OBJ_find_sigid_algs(X509_get_signature_nid(peer_cert),
-							 &algo_nid, NULL))
+#ifdef HAVE_X509_GET_SIGNATURE_NID
+	digest_alg = X509_get_signature_nid(peer_cert);
+#else
+	digest_alg = OBJ_obj2nid(peer_cert->sig_alg->algorithm);
+#endif
+
+	if (!OBJ_find_sigid_algs(digest_alg, &algo_nid, &key_nid))
 	{
 		printfPQExpBuffer(&conn->errorMessage,
 						  libpq_gettext("could not determine server certificate signature algorithm\n"));
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 52a8f458cb..a660615d82 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -20,7 +20,7 @@ my $SERVERHOSTADDR = '127.0.0.1';
 
 # Determine whether build supports tls-server-end-point.
 my $supports_tls_server_end_point =
-  check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+  check_pg_config("#define HAVE_OBJ_FIND_SIGID_ALGS 1");
 
 # Allocation of base connection string shared among multiple tests.
 my $common_connstr;
-- 
2.17.0

