]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Fix handling of SCRAM-SHA-256's channel binding with RSA-PSS certificates
authorMichael Paquier <michael@paquier.xyz>
Wed, 15 Feb 2023 01:12:38 +0000 (10:12 +0900)
committerMichael Paquier <michael@paquier.xyz>
Wed, 15 Feb 2023 01:12:38 +0000 (10:12 +0900)
OpenSSL 1.1.1 and newer versions have added support for RSA-PSS
certificates, which requires the use of a specific routine in OpenSSL to
determine which hash function to use when compiling it when using
channel binding in SCRAM-SHA-256.  X509_get_signature_nid(), that is the
original routine the channel binding code has relied on, is not able to
determine which hash algorithm to use for such certificates.  However,
X509_get_signature_info(), new to OpenSSL 1.1.1, is able to do it.  This
commit switches the channel binding logic to rely on
X509_get_signature_info() over X509_get_signature_nid(), which would be
the choice when building with 1.1.1 or newer.

The error could have been triggered on the client or the server, hence
libpq and the backend need to have their related code paths patched.
Note that attempting to load an RSA-PSS certificate with OpenSSL 1.1.0
or older leads to a failure due to an unsupported algorithm.

The discovery of relying on X509_get_signature_info() comes from Jacob,
the tests have been written by Heikki (with few tweaks from me), while I
have bundled the whole together while adding the bits needed for MSVC
and meson.

This issue exists since channel binding exists, so backpatch all the way
down.  Some tests are added in 15~, triggered if compiling with OpenSSL
1.1.1 or newer, where the certificate and key files can easily be
generated for RSA-PSS.

Reported-by: Gunnar "Nick" Bluth
Author: Jacob Champion, Heikki Linnakangas
Discussion: https://postgr.es/m/17760-b6c61e752ec07060@postgresql.org
Backpatch-through: 11

configure
configure.in
src/backend/libpq/be-secure-openssl.c
src/include/libpq/libpq-be.h
src/include/pg_config.h.in
src/interfaces/libpq/fe-secure-openssl.c
src/interfaces/libpq/libpq-int.h
src/tools/msvc/Solution.pm

index 648e367678fd214fa726faa3f67dab797be7ffcd..7b97a409a8ed5c186ed83d9d2a22ef2e0124568f 100755 (executable)
--- a/configure
+++ b/configure
@@ -12658,6 +12658,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
 
index 91d12c7de42aa136956f7020d25d597d82bc5d03..6a89b798537e5dcb36e92790f565a267e54c9107 100644 (file)
@@ -1292,6 +1292,8 @@ if test "$with_openssl" = yes ; 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])
   # SSL_clear_options is a macro in OpenSSL from 0.9.8 to 1.0.2, and
   # a function from 1.1.0 onwards so we cannot use AC_CHECK_FUNCS.
   AC_CACHE_CHECK([for SSL_clear_options], ac_cv_func_ssl_clear_options,
index 106cde08670cc3be2bc62dedd3bb4b500a552328..b0a1f7258a0bc99a307d21f42fd3bd752cd66fe6 100644 (file)
@@ -1193,7 +1193,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)
 {
@@ -1211,10 +1211,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");
 
        /*
index 96ed175523c26df23f7b3238c37113d2ed7342e3..457fd55cfaff1c01196b0261481427b207a82fdc 100644 (file)
@@ -282,7 +282,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
index 84ee764daf34cb696ea4889eadbcae4465ec0f15..d42f78b11725ed691bdb51523a2eb33db22b47fd 100644 (file)
 /* Define to 1 if you have the <winldap.h> header file. */
 #undef HAVE_WINLDAP_H
 
+/* 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
 
index a19bce8e3c23f2f59e46cd5a69d053e8371272d7..5948a3798380bdec6b0804394765c100aa295a92 100644 (file)
@@ -369,7 +369,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)
 {
@@ -389,10 +389,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
        {
                printfPQExpBuffer(&conn->errorMessage,
                                                  libpq_gettext("could not determine server certificate signature algorithm\n"));
index 1b13e0ab166723156939bc1e6d2ab932b180dd0a..a443418699f8e616df292d2f822b085b54f9b288 100644 (file)
@@ -750,7 +750,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
index 3815a7fb97295f6bfd080dc493b53d9b00941424..04e417901f9aeac490be3e530a853a1946462d8c 100644 (file)
@@ -253,7 +253,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'))
+                       {
+                               print $o "#define HAVE_X509_GET_SIGNATURE_INFO 1\n";
+                       }
+
+                       # Symbols needed with OpenSSL 1.1.0 and above.
                        if (   ($digit1 >= '3' && $digit2 >= '0' && $digit3 >= '0')
                                || ($digit1 >= '1' && $digit2 >= '1' && $digit3 >= '0'))
                        {