From bf83fdbb48b3d5b5ea6013a3a1475818c353e018 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 13 Feb 2023 10:51:52 +0900
Subject: [PATCH] Fix handling of RSS-PSA certificates with channel binding

TODO, write me, please..

This issue exists since channel binding exists, so backpatch all the way
down.

Reported-by: Gunnar "Nick" Bluth
Author: Jacob Champion, Heikki Linnakangas
Discussion: https://postgr.es/m/17760-b6c61e752ec07060@postgresql.org
Backpatch-through: 11
---
 src/include/libpq/libpq-be.h             |  2 +-
 src/include/pg_config.h.in               |  3 +++
 src/backend/libpq/be-secure-openssl.c    |  9 ++++++--
 src/interfaces/libpq/fe-secure-openssl.c |  9 ++++++--
 src/interfaces/libpq/libpq-int.h         |  2 +-
 src/test/ssl/README                      |  1 +
 src/test/ssl/conf/server-rsapss.config   | 14 ++++++++++++
 src/test/ssl/ssl/server-rsapss.crt       | 21 ++++++++++++++++++
 src/test/ssl/ssl/server-rsapss.key       | 28 ++++++++++++++++++++++++
 src/test/ssl/sslfiles.mk                 | 26 ++++++++++++++++++----
 src/test/ssl/t/002_scram.pl              | 17 ++++++++++++++
 configure                                | 12 ++++++++++
 configure.ac                             |  2 ++
 meson.build                              |  3 +++
 src/tools/msvc/Solution.pm               | 10 ++++++++-
 15 files changed, 148 insertions(+), 11 deletions(-)
 create mode 100644 src/test/ssl/conf/server-rsapss.config
 create mode 100644 src/test/ssl/ssl/server-rsapss.crt
 create mode 100644 src/test/ssl/ssl/server-rsapss.key

diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 8c70b2fd5b..ac6407e9f6 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -308,7 +308,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
  * This is not supported with old versions of OpenSSL that don't have
  * the X509_get_signature_nid() function.
  */
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_OPENSSL) && (defined(HAVE_X509_GET_SIGNATURE_NID) || defined(HAVE_X509_GET_SIGNATURE_INFO))
 #define HAVE_BE_TLS_GET_CERTIFICATE_HASH
 extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
 #endif
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index c5a80b829e..2490bf8ace 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -523,6 +523,9 @@
 /* Define to 1 if you have the `wcstombs_l' function. */
 #undef HAVE_WCSTOMBS_L
 
+/* Define to 1 if you have the `X509_get_signature_info' function. */
+#undef HAVE_X509_GET_SIGNATURE_INFO
+
 /* Define to 1 if you have the `X509_get_signature_nid' function. */
 #undef HAVE_X509_GET_SIGNATURE_NID
 
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index e3c7c12aa0..0bde8dd3a9 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -1429,7 +1429,7 @@ be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
 		ptr[0] = '\0';
 }
 
-#ifdef HAVE_X509_GET_SIGNATURE_NID
+#if defined(HAVE_X509_GET_SIGNATURE_NID) || defined(HAVE_X509_GET_SIGNATURE_INFO)
 char *
 be_tls_get_certificate_hash(Port *port, size_t *len)
 {
@@ -1447,10 +1447,15 @@ be_tls_get_certificate_hash(Port *port, size_t *len)
 
 	/*
 	 * Get the signature algorithm of the certificate to determine the hash
-	 * algorithm to use for the result.
+	 * algorithm to use for the result. Prefer X509_get_signature_info(),
+	 * introduced in OpenSSL 1.1.1, which can handle RSA-PSS signatures.
 	 */
+#if HAVE_X509_GET_SIGNATURE_INFO
+	if (!X509_get_signature_info(server_cert, &algo_nid, NULL, NULL, NULL))
+#else
 	if (!OBJ_find_sigid_algs(X509_get_signature_nid(server_cert),
 							 &algo_nid, NULL))
+#endif
 		elog(ERROR, "could not determine server certificate signature algorithm");
 
 	/*
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 983536de25..40829177c0 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -364,7 +364,7 @@ pgtls_write(PGconn *conn, const void *ptr, size_t len)
 	return n;
 }
 
-#ifdef HAVE_X509_GET_SIGNATURE_NID
+#if defined(HAVE_X509_GET_SIGNATURE_NID) || defined(HAVE_X509_GET_SIGNATURE_INFO)
 char *
 pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
 {
@@ -384,10 +384,15 @@ pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
 
 	/*
 	 * Get the signature algorithm of the certificate to determine the hash
-	 * algorithm to use for the result.
+	 * algorithm to use for the result. Prefer X509_get_signature_info(),
+	 * introduced in OpenSSL 1.1.1, which can handle RSA-PSS signatures.
 	 */
+#if HAVE_X509_GET_SIGNATURE_INFO
+	if (!X509_get_signature_info(peer_cert, &algo_nid, NULL, NULL, NULL))
+#else
 	if (!OBJ_find_sigid_algs(X509_get_signature_nid(peer_cert),
 							 &algo_nid, NULL))
+#endif
 	{
 		libpq_append_conn_error(conn, "could not determine server certificate signature algorithm");
 		return NULL;
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index d94b648ea5..d7ec5ed429 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -806,7 +806,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
  * This is not supported with old versions of OpenSSL that don't have
  * the X509_get_signature_nid() function.
  */
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_OPENSSL) && (defined(HAVE_X509_GET_SIGNATURE_NID) || defined(HAVE_X509_GET_SIGNATURE_INFO))
 #define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
 extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
 #endif
diff --git a/src/test/ssl/README b/src/test/ssl/README
index b328203c7c..2101a466d2 100644
--- a/src/test/ssl/README
+++ b/src/test/ssl/README
@@ -92,6 +92,7 @@ ssl/ subdirectory. The Makefile also contains a rule, "make sslfiles", to
 recreate them if you need to make changes. "make sslfiles-clean" is required
 in order to recreate the full set of keypairs and certificates. To rebuild
 separate files, touch (or remove) the files in question and run "make sslfiles".
+This step requires at least OpenSSL 1.1.1.
 
 Note
 ====
diff --git a/src/test/ssl/conf/server-rsapss.config b/src/test/ssl/conf/server-rsapss.config
new file mode 100644
index 0000000000..391f9b8d89
--- /dev/null
+++ b/src/test/ssl/conf/server-rsapss.config
@@ -0,0 +1,14 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This is identical to server-cn-only certificate, but we specify
+# RSA-PSS as the algorithm on the command line.
+
+[ req ]
+distinguished_name     = req_distinguished_name
+prompt                 = no
+
+[ req_distinguished_name ]
+CN = common-name.pg-ssltest.test
+OU = PostgreSQL test suite
+
+# No Subject Alternative Names
\ No newline at end of file
diff --git a/src/test/ssl/ssl/server-rsapss.crt b/src/test/ssl/ssl/server-rsapss.crt
new file mode 100644
index 0000000000..1c35956d57
--- /dev/null
+++ b/src/test/ssl/ssl/server-rsapss.crt
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDezCCAi4CFCrZutHsw0Vl3OCgOmvtL0I/XAZyMEIGCSqGSIb3DQEBCjA1oA8w
+DQYJYIZIAWUDBAIBBQChHDAaBgkqhkiG9w0BAQgwDQYJYIZIAWUDBAIBBQCiBAIC
+AN4wRjEkMCIGA1UEAwwbY29tbW9uLW5hbWUucGctc3NsdGVzdC50ZXN0MR4wHAYD
+VQQLDBVQb3N0Z3JlU1FMIHRlc3Qgc3VpdGUwHhcNMjMwMjEzMDEyMjA2WhcNMjMw
+MzE1MDEyMjA2WjBGMSQwIgYDVQQDDBtjb21tb24tbmFtZS5wZy1zc2x0ZXN0LnRl
+c3QxHjAcBgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTCCASAwCwYJKoZIhvcN
+AQEKA4IBDwAwggEKAoIBAQC6YtrZZukJ4n31gKpcIOl65D9roe2jzcIBX1AZq1fR
+I6qmt7aR0iFCKEy9D2fs6lM+NVQSurg7b0gKL+XoOadySAxALIrUwcCQM7rZvUR0
+aKo3Qm0U00ir4x0i73/sTpY25zBSFoqGldmlqiIIWxpe8hqZEc6Sc78Bs2FaAa9A
+5sTLaX5nG6jyreJweLcmv+TYFVqxNq7Y7tC67zWXr6r49JBkSHSibzBr/uFxOGsP
+B9hwGo4/foACjeDNAT0vjwMLnV19Sd2zf9daBo+sd9bCj2C5CpOyXxFtO7cMh0tP
+U3ZqcYPViFxcPObmhnJgqlBbgZD/WLxm1aFgUYjqMQ47AgMBAAEwQgYJKoZIhvcN
+AQEKMDWgDzANBglghkgBZQMEAgEFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQME
+AgEFAKIEAgIA3gOCAQEAQpYu7fz9iz8CplCOp4SJ1eO9UjbtdxzvuaVR751TfYrX
+OO19jq7YyWgqJDwROnDJBFEy9B+HaXTfscEHpGIHAIpx7S7az/gLnO90HshXcK+/
+CbjW9axRB9TrD2zOrISl9NSuEZ5tbd5/Ml2yzY85CCjYPuNy+euH5XgcXcwF3Q49
+G5eDJnaCCYzwdEOZY8ris9o9go8aL6zNAfhUKToRUfeoBCStOLZSgb6d/IKRB9eg
+M0FImsMI3j5zHCiH0HhMwCRFRuZqTp1EMBHANIJncTZSGWQyKQ71zO/l/3YzwNfm
+c2gyeh0DJWFkEZD3spWs8K6UEoTESP6Ivj47LmnWjg==
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-rsapss.key b/src/test/ssl/ssl/server-rsapss.key
new file mode 100644
index 0000000000..a5bc297f1d
--- /dev/null
+++ b/src/test/ssl/ssl/server-rsapss.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADALBgkqhkiG9w0BAQoEggSpMIIEpQIBAAKCAQEAumLa2WbpCeJ99YCq
+XCDpeuQ/a6Hto83CAV9QGatX0SOqpre2kdIhQihMvQ9n7OpTPjVUErq4O29ICi/l
+6DmnckgMQCyK1MHAkDO62b1EdGiqN0JtFNNIq+MdIu9/7E6WNucwUhaKhpXZpaoi
+CFsaXvIamRHOknO/AbNhWgGvQObEy2l+Zxuo8q3icHi3Jr/k2BVasTau2O7Quu81
+l6+q+PSQZEh0om8wa/7hcThrDwfYcBqOP36AAo3gzQE9L48DC51dfUnds3/XWgaP
+rHfWwo9guQqTsl8RbTu3DIdLT1N2anGD1YhcXDzm5oZyYKpQW4GQ/1i8ZtWhYFGI
+6jEOOwIDAQABAoIBAAPXZpi55PdieTXUQpxPxDJpx01p4IdAKoRzS3EwkP99d/sR
+qNCekaUyIW9UqT2Hx2Tb1MzCBUZQ40I1614fehK5C2sFdtnls8/gdaIe7FqwIYxA
+lcxhpvjHX2Ht8gLc8OvpC5vDOJkZymZsHM8qa8zcTD/AzzNBOpdHqwdES58YoqEb
+5LOVLBRIoLli2eAWrrnoYl7MQuh3CHHtWGjn3drTzg6Tl2umfNhTMFANZssNexl4
+6npPHBASdevWWsqB8GXD56PaqWxxnjtwzk06lRbloSQYJOicI8OK7eaySpRuHpZV
+3vJKhY3bcRN6joxveXA7jaAPSBvNXp2w5fQ1b2ECgYEA1mzqOCln87aaLzZ1KlWL
+QfxcXmcke1lJgbhW+iEh6iht2OmBlntAlIVv/D3yBDhNrHdrNlUcWvm+VSrbVyxn
+6e1RWHAGPzZNhpcg4odxdI6Oton/OBtsEQ7A6UJ6S7bPTVGVwi9fA4fI0Pfne0wV
+IeJHvjDZboOBi6TF2thcJ2sCgYEA3oYzAt4tEiA+nQyNnP4nWZ17XONA6H8yVeUY
+Sk6eczg8eGAQz9afVtbSI3uRIfQbQ1+mjaUl4pVej2UDXcROpYHgwCLJRBBDbzzB
+4IcPh2woFGZOScQu9Q64C8g6MH4zm3WkFvXyJF3j3dHGFZGq8nmwEARJgAsQ6Yig
+kYL8+HECgYEAtuKUbqxaPlL7dNNU4XOu3+v3eIkuY4qHGH36qUKDI62x6zVWUtvy
++/pHxnOrLRA8p6H/LosvMSUbwpZYGCUGyE2iePSrT1TokKfr42o0SX6hmG1g4iD5
+bh8QSKNrnZJhg4fXXJV8y40PqbQXmmENESZnnH8bpJfDcTBrlLm+99sCgYEA3F1f
+xPZLAglGmHZnA1K5m0iWc01l6RiVu3RNksC6r3XAhKD15S0wzGme3p6vAkXgfd8K
+bHlgxDuR0kWBiOkvzT2KWhvY3vuQHGe5w+VcnoqgQltyKiELM4mo/5oA7ib8anac
+0lQrwJHuZ6wnExMXjFqv3ZyxQQk0bWDtSkzCwjECgYEAusqqCAmryRFWdOif2z+Z
+3vfseSvBdQMj2FO7weqCVPV4Gnae0TO7A1bUpVX/pfkDEPitt5oUgS2KTozW5vwz
+yaQTSB8RO8EG66GURZvPs3Cerkyrgk/OMmbCv3B0ALwhPMBqpemJqeBOuyaAjY8W
+Tqb6E2ofRlYND0xH83gCTig=
+-----END PRIVATE KEY-----
diff --git a/src/test/ssl/sslfiles.mk b/src/test/ssl/sslfiles.mk
index 5d9dc09a4b..e63342469d 100644
--- a/src/test/ssl/sslfiles.mk
+++ b/src/test/ssl/sslfiles.mk
@@ -37,13 +37,17 @@ CLIENTS := client client-dn client-revoked client_ext client-long \
 	client-revoked-utf8
 
 #
-# To add a new non-standard key, add it to SPECIAL_KEYS and then add a recipe
-# for creating it to the "Special-case keys" section below.
+# To add a new non-standard certificate, add it to SPECIAL_CERTS and then add
+# a recipe for creating it to the "Special-case certificates" section below.
 #
+SPECIAL_CERTS := ssl/server-rsapss.crt
+
+# Likewise for non-standard keys
 SPECIAL_KEYS := ssl/server-password.key \
 	ssl/client-der.key \
 	ssl/client-encrypted-pem.key \
-	ssl/client-encrypted-der.key
+	ssl/client-encrypted-der.key \
+	ssl/server-rsapss.key
 
 #
 # These files are just concatenations of other files. You can add new ones to
@@ -66,7 +70,13 @@ CRLS := ssl/root.crl \
 	ssl/client.crl \
 	ssl/server.crl
 
-SSLFILES := $(STANDARD_CERTS) $(STANDARD_KEYS) $(SPECIAL_KEYS) $(COMBINATIONS) $(CRLS)
+SSLFILES := \
+	$(STANDARD_CERTS) \
+	$(STANDARD_KEYS) \
+	$(SPECIAL_CERTS) \
+	$(SPECIAL_KEYS) \
+	$(COMBINATIONS) \
+	$(CRLS)
 SSLDIRS := ssl/client-crldir \
 	ssl/server-crldir \
 	ssl/root+client-crldir \
@@ -86,6 +96,10 @@ sslfiles: $(SSLFILES) $(SSLDIRS)
 ssl/root_ca.crt: ssl/root_ca.key conf/root_ca.config
 	$(OPENSSL) req -new -x509 -config conf/root_ca.config -days 10000 -key $< -out $@
 
+# Certificate using RSA-PSS algorithm. Also self-signed.
+ssl/server-rsapss.crt: ssl/server-rsapss.key conf/server-rsapss.config
+	$(OPENSSL) req -new -x509 -config conf/server-rsapss.config -key $< -out $@
+
 #
 # Special-case keys
 #
@@ -96,6 +110,10 @@ ssl/root_ca.crt: ssl/root_ca.key conf/root_ca.config
 ssl/server-password.key: ssl/server-cn-only.key
 	$(OPENSSL) rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
 
+# Key that uses the RSA-PSS algorithm
+ssl/server-rsapss.key:
+	$(OPENSSL) genpkey -algorithm rsa-pss -out $@
+
 # DER-encoded version of client.key
 ssl/client-der.key: ssl/client.key
 	$(OPENSSL) rsa -in $< -outform DER -out $@
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 0f3d180cfa..1d3905d3a1 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -46,6 +46,10 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
 # Determine whether build supports tls-server-end-point.
 my $supports_tls_server_end_point =
   check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+# Determine whether build supports detection of hash algorithms for
+# RSA-PSS certificates.
+my $supports_rsapss_certs =
+  check_pg_config("#define HAVE_X509_GET_SIGNATURE_INFO 1");
 
 # Allocation of base connection string shared among multiple tests.
 my $common_connstr;
@@ -136,4 +140,17 @@ $node->connect_ok(
 		qr/connection authenticated: identity="ssltestuser" method=scram-sha-256/
 	]);
 
+# Now test with a server certificate that uses the RSA-PSS algorithm.
+# This checks that the certificate can be loaded and that channel binding
+# works. (see bug #17760)
+if ($supports_rsapss_certs)
+{
+	switch_server_cert($node, certfile => 'server-rsapss');
+	$node->connect_ok(
+		"$common_connstr user=ssltestuser channel_binding=require",
+		"SCRAM with SSL and channel_binding=require, server certificate uses 'rsassaPss'",
+		log_like => [
+			qr/connection authenticated: identity="ssltestuser" method=scram-sha-256/
+		]);
+}
 done_testing();
diff --git a/configure b/configure
index 5d07fd0bb9..7bb829ddf4 100755
--- a/configure
+++ b/configure
@@ -13013,6 +13013,18 @@ if test "x$ac_cv_func_CRYPTO_lock" = xyes; then :
 #define HAVE_CRYPTO_LOCK 1
 _ACEOF
 
+fi
+done
+
+  # Function introduced in OpenSSL 1.1.1.
+  for ac_func in X509_get_signature_info
+do :
+  ac_fn_c_check_func "$LINENO" "X509_get_signature_info" "ac_cv_func_X509_get_signature_info"
+if test "x$ac_cv_func_X509_get_signature_info" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_X509_GET_SIGNATURE_INFO 1
+_ACEOF
+
 fi
 done
 
diff --git a/configure.ac b/configure.ac
index e9b74ced6c..137a40a942 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1385,6 +1385,8 @@ if test "$with_ssl" = openssl ; then
   # thread-safety. In 1.1.0, it's no longer required, and CRYPTO_lock()
   # function was removed.
   AC_CHECK_FUNCS([CRYPTO_lock])
+  # Function introduced in OpenSSL 1.1.1.
+  AC_CHECK_FUNCS([X509_get_signature_info])
   AC_DEFINE([USE_OPENSSL], 1, [Define to 1 to build with OpenSSL support. (--with-ssl=openssl)])
 elif test "$with_ssl" != no ; then
   AC_MSG_ERROR([--with-ssl must specify openssl])
diff --git a/meson.build b/meson.build
index e379a252a5..b5daed9f38 100644
--- a/meson.build
+++ b/meson.build
@@ -1223,6 +1223,9 @@ if get_option('ssl') == 'openssl'
     # thread-safety. In 1.1.0, it's no longer required, and CRYPTO_lock()
     # function was removed.
     ['CRYPTO_lock'],
+
+    # Function introduced in OpenSSL 1.1.1
+    ['X509_get_signature_info'],
   ]
 
   foreach c : check_funcs
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index 355ea0f9e5..5399050492 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -371,6 +371,7 @@ sub GenerateFiles
 		HAVE_WCSTOMBS_L                          => 1,
 		HAVE_VISIBILITY_ATTRIBUTE                => undef,
 		HAVE_X509_GET_SIGNATURE_NID              => 1,
+		HAVE_X509_GET_SIGNATURE_INFO             => undef,
 		HAVE_X86_64_POPCNTQ                      => undef,
 		HAVE__BOOL                               => undef,
 		HAVE__BUILTIN_BSWAP16                    => undef,
@@ -489,7 +490,14 @@ sub GenerateFiles
 
 		my ($digit1, $digit2, $digit3) = $self->GetOpenSSLVersion();
 
-		# More symbols are needed with OpenSSL 1.1.0 and above.
+		# Symbols needed with OpenSSL 1.1.1 and above.
+		if (   ($digit1 >= '3' && $digit2 >= '0' && $digit3 >= '0')
+			|| ($digit1 >= '1' && $digit2 >= '1' && $digit3 >= '1'))
+		{
+			$define{HAVE_X509_GET_SIGNATURE_INFO} = 1;
+		}
+
+		# Symbols needed with OpenSSL 1.1.0 and above.
 		if (   ($digit1 >= '3' && $digit2 >= '0' && $digit3 >= '0')
 			|| ($digit1 >= '1' && $digit2 >= '1' && $digit3 >= '0'))
 		{
-- 
2.39.1

