Skip to content

Commit

Permalink
Fix handling of SCRAM-SHA-256's channel binding with RSA-PSS certific…
Browse files Browse the repository at this point in the history
…ates

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
  • Loading branch information
michaelpq committed Feb 15, 2023
1 parent 8d2a858 commit 88d606f
Show file tree
Hide file tree
Showing 8 changed files with 41 additions and 7 deletions.
12 changes: 12 additions & 0 deletions configure
Expand Up @@ -12316,6 +12316,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

Expand Down
2 changes: 2 additions & 0 deletions configure.in
Expand Up @@ -1287,6 +1287,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,
Expand Down
9 changes: 7 additions & 2 deletions src/backend/libpq/be-secure-openssl.c
Expand Up @@ -1127,7 +1127,7 @@ be_tls_get_peerdn_name(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)
{
Expand All @@ -1145,10 +1145,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");

/*
Expand Down
2 changes: 1 addition & 1 deletion src/include/libpq/libpq-be.h
Expand Up @@ -270,7 +270,7 @@ extern void be_tls_get_peerdn_name(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
Expand Down
3 changes: 3 additions & 0 deletions src/include/pg_config.h.in
Expand Up @@ -715,6 +715,9 @@
/* 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

Expand Down
9 changes: 7 additions & 2 deletions src/interfaces/libpq/fe-secure-openssl.c
Expand Up @@ -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)
{
Expand All @@ -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"));
Expand Down
2 changes: 1 addition & 1 deletion src/interfaces/libpq/libpq-int.h
Expand Up @@ -725,7 +725,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
Expand Down
9 changes: 8 additions & 1 deletion src/tools/msvc/Solution.pm
Expand Up @@ -257,7 +257,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'))
{
Expand Down

0 comments on commit 88d606f

Please sign in to comment.