Fix handling of SCRAM-SHA-256's channel binding with RSA-PSS certificates
authorMichael Paquier <[email protected]>
Wed, 15 Feb 2023 01:12:36 +0000 (10:12 +0900)
committerMichael Paquier <[email protected]>
Wed, 15 Feb 2023 01:12:36 +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 19a991aeca7598e89fa5ab8bcbc9b851a41c848c..e68498e6cf6913792ad77f68870b160c1d46307f 100755 (executable)
--- a/configure
+++ b/configure
@@ -12730,6 +12730,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 3e2767ae52ee5691fb4c294e8f2e5f4e67991529..4da9b4a89b9a990bdcdfcc02116cee0f9e038bff 100644 (file)
@@ -1277,6 +1277,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])
 fi
 
 if test "$with_pam" = yes ; then
index 3b1a5b1b9b2a0df55802da93c5f74fcde33a28bc..55fe59276a94003463b4115943681d414748a0ed 100644 (file)
@@ -1243,7 +1243,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)
 {
@@ -1261,10 +1261,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 fa778e11921bc1f73c54099461eae28d22e42578..fcfe9b342faf539e7debe497050badf7689150ad 100644 (file)
@@ -283,7 +283,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 2cd221b85a0143f2381453bac23f6e48b3b235b9..745cca5b0501995e5df915ce0e36d8490338db50 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 26615a1aa4241eb5eb3e10b116b8ec83a2d097c9..07d5daf4d9b49216ee2b632c0bbae304c2d2352f 100644 (file)
@@ -371,7 +371,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)
 {
@@ -391,10 +391,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 1de91ae295b38597fe804dd1b31f207ee0d27169..efce6c6afd771e92a973914a19079c4f775da0cc 100644 (file)
@@ -755,7 +755,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 4147e8ad2604bf6da5d97ad5ebd67c3850ea93bf..54537411aba116db535e540a46d71d07deed4d0e 100644 (file)
@@ -412,6 +412,7 @@ sub GenerateFiles
        HAVE_WCSTOMBS_L                          => 1,
        HAVE_WCTYPE_H                            => 1,
        HAVE_X509_GET_SIGNATURE_NID              => 1,
+       HAVE_X509_GET_SIGNATURE_INFO             => undef,
        HAVE_X86_64_POPCNTQ                      => undef,
        HAVE__BOOL                               => undef,
        HAVE__BUILTIN_BSWAP16                    => undef,
@@ -522,7 +523,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'))
        {