+
+ sslcertmode
+
+ This option determines whether a client certificate may be sent to the
+ server, and whether the server is required to request one. There are
+ three modes:
+
+
+
+ disable
+
+ A client certificate is never sent, even if one is available
+ (default location or provided via
+ ).
+
+
+
+
+
+ allow (default)
+
+ A certificate may be sent, if the server requests one and the
+ client has one to send.
+
+
+
+
+
+ require
+
+ The server must request a certificate. The
+ connection will fail if the client does not send a certificate and
+ the server successfully authenticates the client anyway.
+
+
+
+
+
+
+
+ sslcertmode=require doesn't add any additional
+ security, since there is no guarantee that the server is validating
+ the certificate correctly; PostgreSQL servers generally request TLS
+ certificates from clients whether they validate them or not. The
+ option may be useful when troubleshooting more complicated TLS
+ setups.
+
+
+
+
+
sslrootcert
+
+
+
+
PGSSLCERTMODE behaves the same as the
+ linkend="libpq-connect-sslcertmode"/> connection parameter.
+
+
+
['CRYPTO_new_ex_data', {'required': true}],
['SSL_new', {'required': true}],
- # Function introduced in OpenSSL 1.0.2.
+ # Functions introduced in OpenSSL 1.0.2.
['X509_get_signature_nid'],
+ ['SSL_CTX_set_cert_cb'], # not in LibreSSL
# 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
/* Define to 1 if you have spinlocks. */
#undef HAVE_SPINLOCKS
+/* Define to 1 if you have the `SSL_CTX_set_cert_cb' function. */
+#undef HAVE_SSL_CTX_SET_CERT_CB
+
/* Define to 1 if stdbool.h conforms to C99. */
#undef HAVE_STDBOOL_H
StaticAssertDecl((sizeof(conn->allowed_auth_methods) * CHAR_BIT) > AUTH_REQ_MAX,
"AUTH_REQ_MAX overflows the allowed_auth_methods bitmask");
+ if (conn->sslcertmode[0] == 'r' /* require */
+ && areq == AUTH_REQ_OK)
+ {
+ /*
+ * Trade off a little bit of complexity to try to get these error
+ * messages as precise as possible.
+ */
+ if (!conn->ssl_cert_requested)
+ {
+ libpq_append_conn_error(conn, "server did not request an SSL certificate");
+ return false;
+ }
+ else if (!conn->ssl_cert_sent)
+ {
+ libpq_append_conn_error(conn, "server accepted connection without a valid SSL certificate");
+ return false;
+ }
+ }
+
/*
* If the user required a specific auth method, or specified an allowed
* set, then reject all others here, and make sure the server actually
#define DefaultTargetSessionAttrs "any"
#ifdef USE_SSL
#define DefaultSSLMode "prefer"
+#define DefaultSSLCertMode "allow"
#else
#define DefaultSSLMode "disable"
+#define DefaultSSLCertMode "disable"
#endif
#ifdef ENABLE_GSS
#include "fe-gssapi-common.h"
"SSL-Client-Key", "", 64,
offsetof(struct pg_conn, sslkey)},
+ {"sslcertmode", "PGSSLCERTMODE", NULL, NULL,
+ "SSL-Client-Cert-Mode", "", 8, /* sizeof("disable") == 8 */
+ offsetof(struct pg_conn, sslcertmode)},
+
{"sslpassword", NULL, NULL, NULL,
"SSL-Client-Key-Password", "*", 20,
offsetof(struct pg_conn, sslpassword)},
return false;
}
+ /*
+ * validate sslcertmode option
+ */
+ if (conn->sslcertmode)
+ {
+ if (strcmp(conn->sslcertmode, "disable") != 0 &&
+ strcmp(conn->sslcertmode, "allow") != 0 &&
+ strcmp(conn->sslcertmode, "require") != 0)
+ {
+ conn->status = CONNECTION_BAD;
+ libpq_append_conn_error(conn, "invalid %s value: \"%s\"",
+ "sslcertmode", conn->sslcertmode);
+ return false;
+ }
+#ifndef USE_SSL
+ if (strcmp(conn->sslcertmode, "require") == 0)
+ {
+ conn->status = CONNECTION_BAD;
+ libpq_append_conn_error(conn, "%s value \"%s\" invalid when SSL support is not compiled in",
+ "sslcertmode", conn->sslcertmode);
+ return false;
+ }
+#endif
+#ifndef HAVE_SSL_CTX_SET_CERT_CB
+
+ /*
+ * Without a certificate callback, the current implementation can't
+ * figure out if a certificate was actually requested, so "require" is
+ * useless.
+ */
+ if (strcmp(conn->sslcertmode, "require") == 0)
+ {
+ conn->status = CONNECTION_BAD;
+ libpq_append_conn_error(conn, "%s value \"%s\" is not supported (check OpenSSL version)",
+ "sslcertmode", conn->sslcertmode);
+ return false;
+ }
+#endif
+ }
+ else
+ {
+ conn->sslcertmode = strdup(DefaultSSLCertMode);
+ if (!conn->sslcertmode)
+ goto oom_error;
+ }
+
/*
* validate gssencmode option
*/
explicit_bzero(conn->sslpassword, strlen(conn->sslpassword));
free(conn->sslpassword);
}
+ free(conn->sslcertmode);
free(conn->sslrootcert);
free(conn->sslcrl);
free(conn->sslcrldir);
return ok;
}
+#ifdef HAVE_SSL_CTX_SET_CERT_CB
+/*
+ * Certificate selection callback
+ *
+ * This callback lets us choose the client certificate we send to the server
+ * after seeing its CertificateRequest. We only support sending a single
+ * hard-coded certificate via sslcert, so we don't actually set any certificates
+ * here; we just use it to record whether or not the server has actually asked
+ * for one and whether we have one to send.
+ */
+static int
+cert_cb(SSL *ssl, void *arg)
+{
+ PGconn *conn = arg;
+
+ conn->ssl_cert_requested = true;
+
+ /* Do we have a certificate loaded to send back? */
+ if (SSL_get_certificate(ssl))
+ conn->ssl_cert_sent = true;
+
+ /*
+ * Tell OpenSSL that the callback succeeded; we're not required to
+ * actually make any changes to the SSL handle.
+ */
+ return 1;
+}
+#endif
/*
* OpenSSL-specific wrapper around
SSL_CTX_set_default_passwd_cb_userdata(SSL_context, conn);
}
+#ifdef HAVE_SSL_CTX_SET_CERT_CB
+ /* Set up a certificate selection callback. */
+ SSL_CTX_set_cert_cb(SSL_context, cert_cb, conn);
+#endif
+
/* Disable old protocol versions */
SSL_CTX_set_options(SSL_context, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
else
fnbuf[0] = '\0';
- if (fnbuf[0] == '\0')
+ if (conn->sslcertmode[0] == 'd') /* disable */
+ {
+ /* don't send a client cert even if we have one */
+ have_cert = false;
+ }
+ else if (fnbuf[0] == '\0')
{
/* no home directory, proceed without a client cert */
have_cert = false;
char *sslkey; /* client key filename */
char *sslcert; /* client certificate filename */
char *sslpassword; /* client key file password */
+ char *sslcertmode; /* client cert mode (require,allow,disable) */
char *sslrootcert; /* root certificate filename */
char *sslcrl; /* certificate revocation list filename */
char *sslcrldir; /* certificate revocation list directory name */
/* SSL structures */
bool ssl_in_use;
+ bool ssl_cert_requested; /* Did the server ask us for a cert? */
+ bool ssl_cert_sent; /* Did we send one in reply? */
#ifdef USE_SSL
bool allow_ssl_try; /* Allowed to try SSL negotiation */
# This is the pattern to use in pg_hba.conf to match incoming connections.
my $SERVERHOSTCIDR = '127.0.0.1/32';
+# Determine whether build supports sslcertmode=require.
+my $supports_sslcertmode_require =
+ check_pg_config("#define HAVE_SSL_CTX_SET_CERT_CB 1");
+
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
"$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
"cert root file that contains two certificates, order 2");
+# sslcertmode=allow and disable should both work without a client certificate.
+$node->connect_ok(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require sslcertmode=disable",
+ "connect with sslcertmode=disable");
+$node->connect_ok(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require sslcertmode=allow",
+ "connect with sslcertmode=allow");
+
+# sslcertmode=require, however, should fail.
+$node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require sslcertmode=require",
+ "connect with sslcertmode=require fails without a client certificate",
+ expected_stderr => $supports_sslcertmode_require
+ ? qr/server accepted connection without a valid SSL certificate/
+ : qr/sslcertmode value "require" is not supported/);
+
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
"certificate authorization succeeds with correct client cert in encrypted DER format"
);
+# correct client cert with sslcertmode=allow or require
+if ($supports_sslcertmode_require)
+{
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcertmode=require sslcert=ssl/client.crt "
+ . sslkey('client.key'),
+ "certificate authorization succeeds with correct client cert and sslcertmode=require"
+ );
+}
+$node->connect_ok(
+ "$common_connstr user=ssltestuser sslcertmode=allow sslcert=ssl/client.crt "
+ . sslkey('client.key'),
+ "certificate authorization succeeds with correct client cert and sslcertmode=allow"
+);
+
+# client cert is not sent if sslcertmode=disable.
+$node->connect_fails(
+ "$common_connstr user=ssltestuser sslcertmode=disable sslcert=ssl/client.crt "
+ . sslkey('client.key'),
+ "certificate authorization fails with correct client cert and sslcertmode=disable",
+ expected_stderr => qr/connection requires a valid client certificate/);
+
# correct client cert in encrypted PEM with wrong password
$node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client.crt "
# This is the pattern to use in pg_hba.conf to match incoming connections.
my $SERVERHOSTCIDR = '127.0.0.1/32';
+# Determine whether build supports sslcertmode=require.
+my $supports_sslcertmode_require =
+ check_pg_config("#define HAVE_SSL_CTX_SET_CERT_CB 1");
+
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
connstr => $common_connstr);
is($result, 'CA:FALSE|t', 'extract extension from cert');
+# Sanity tests for sslcertmode, using ssl_client_cert_present()
+my @cases = (
+ { opts => "sslcertmode=allow", present => 't' },
+ { opts => "sslcertmode=allow sslcert=invalid", present => 'f' },
+ { opts => "sslcertmode=disable", present => 'f' },);
+if ($supports_sslcertmode_require)
+{
+ push(@cases, { opts => "sslcertmode=require", present => 't' });
+}
+
+foreach my $c (@cases)
+{
+ $result = $node->safe_psql(
+ "trustdb",
+ "SELECT ssl_client_cert_present();",
+ connstr => "$common_connstr dbname=trustdb $c->{'opts'}");
+ is($result, $c->{'present'},
+ "ssl_client_cert_present() for $c->{'opts'}");
+}
+
done_testing();
HAVE_SETPROCTITLE_FAST => undef,
HAVE_SOCKLEN_T => 1,
HAVE_SPINLOCKS => 1,
+ HAVE_SSL_CTX_SET_CERT_CB => undef,
HAVE_STDBOOL_H => 1,
HAVE_STDINT_H => 1,
HAVE_STDLIB_H => 1,
$define{HAVE_HMAC_CTX_NEW} = 1;
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
+
+ # Symbols needed with OpenSSL 1.0.2 and above.
+ if ( ($digit1 >= '3' && $digit2 >= '0' && $digit3 >= '0')
+ || ($digit1 >= '1' && $digit2 >= '1' && $digit3 >= '0')
+ || ($digit1 >= '1' && $digit2 >= '0' && $digit3 >= '2'))
+ {
+ $define{HAVE_SSL_CTX_SET_CERT_CB} = 1;
+ }
}
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);