diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index 53832d08e2..5ee8cbe01a 100644
--- a/doc/src/sgml/client-auth.sgml
+++ b/doc/src/sgml/client-auth.sgml
@@ -563,10 +563,17 @@ hostnossl database user
In addition to the method-specific options listed below, there is one
method-independent authentication option clientcert, which
- can be specified in any hostssl record. When set
- to 1, this option requires the client to present a valid
- (trusted) SSL certificate, in addition to the other requirements of the
- authentication method.
+ can be specified in any hostssl record.
+ This option can be set to verify-ca or
+ verify-full. Both options require the client
+ to present a valid (trusted) SSL certificate, while
+ verify-full additionally enforces that the
+ CN (Common Name) in the certificate matches
+ the username or an applicable mapping.
+ This behaviour is similar to the cert autentication method
+ (see ) but enables pairing
+ the verification of client certificates with any authentication
+ method that supports hostssl entries.
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 587b430527..19af9bd7e0 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -2263,13 +2263,25 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
(CAs) you trust in a file in the data
directory, set the parameter in
postgresql.conf to the new file name, and add the
- authentication option clientcert=1 to the appropriate
+ authentication option clientcert=verify-ca or
+ clientcert=verify-full to the appropriate
hostssl line(s) in pg_hba.conf.
A certificate will then be requested from the client during SSL
connection startup. (See for a description
- of how to set up certificates on the client.) The server will
- verify that the client's certificate is signed by one of the trusted
- certificate authorities.
+ of how to set up certificates on the client.)
+
+
+
+ For a hostssl entry with
+ clientcert=verify-ca, the server will verify
+ that the client's certificate is signed by one of the trusted
+ certificate authorities. If clientcert=verify-full
+ is specified, the server will not only verify the certificate
+ chain, but it will also check whether the username or it's
+ mapping match the common name (CN) of the provided certificate.
+ Note that certificate chain validation is always ensured when
+ cert authentication method is used
+ (see ).
@@ -2293,14 +2305,33 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
client certificates against its CA file, if one is configured — but
it will not insist that a client certificate be presented.
+
+
+
+ Enforcing Client Certificates
+
+ There are two approaches to enforce that users provide a certificate during login.
+
+
+
+ The first approach makes use of the cert authentication
+ method for hostssl entries in pg_hba.conf,
+ such that the certificate itself is used for authentication while also
+ providing ssl connection security. See for details.
+ (It is not necessary to specify any clientcert
+ options explicitly when using the cert authentication method.)
+ In this case, the CN (nommon name) provided in
+ the certificate is checked against the user name or an applicable mapping.
+
- If you are setting up client certificates, you may wish to use
- the cert authentication method, so that the certificates
- control user authentication as well as providing connection security.
- See for details. (It is not necessary to
- specify clientcert=1 explicitly when using
- the cert authentication method.)
+ The second approach combines any authentication method for hostssl
+ entries with the verification of client certificates by setting the
+ clientcert authentication option to verify-ca
+ or verify-full. The former option only enforces that
+ the certificate is valid, while the latter also ensures that the
+ CN (Common Name) in the certificate matches
+ the user name or an applicable mapping.
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 3014b17a7c..f6c3ff01b2 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -347,6 +347,7 @@ void
ClientAuthentication(Port *port)
{
int status = STATUS_ERROR;
+ int status_verify_cert_full = STATUS_ERROR;
char *logdetail = NULL;
/*
@@ -364,7 +365,7 @@ ClientAuthentication(Port *port)
* current connection, so perform any verifications based on the hba
* options field that should be done *before* the authentication here.
*/
- if (port->hba->clientcert)
+ if (port->hba->clientcert != clientCertOff)
{
/* If we haven't loaded a root certificate store, fail */
if (!secure_loaded_verify_locations())
@@ -600,10 +601,23 @@ ClientAuthentication(Port *port)
break;
}
+ if(port->hba->clientcert == clientCertFull && port->hba->auth_method!=uaCert)
+ {
+#ifdef USE_SSL
+ status_verify_cert_full = CheckCertAuth(port);
+#else
+ Assert(false);
+#endif
+ }
+ else
+ {
+ status_verify_cert_full = STATUS_OK;
+ }
+
if (ClientAuthentication_hook)
(*ClientAuthentication_hook) (port, status);
- if (status == STATUS_OK)
+ if (status == STATUS_OK && status_verify_cert_full == STATUS_OK)
sendAuthRequest(port, AUTH_REQ_OK, NULL, 0);
else
auth_failed(port, status, logdetail);
@@ -2756,6 +2770,7 @@ errdetail_for_ldap(LDAP *ldap)
static int
CheckCertAuth(Port *port)
{
+ int status_check_usermap = STATUS_ERROR;
Assert(port->ssl);
/* Make sure we have received a username in the certificate */
@@ -2769,7 +2784,21 @@ CheckCertAuth(Port *port)
}
/* Just pass the certificate CN to the usermap check */
- return check_usermap(port->hba->usermap, port->user_name, port->peer_cn, false);
+ status_check_usermap = check_usermap(port->hba->usermap, port->user_name, port->peer_cn, false);
+ if (status_check_usermap != STATUS_OK)
+ {
+ /*
+ * If clientcert=verify-full was specified and the authentication method
+ * is other than uaCert, log the reason for rejecting the authentication.
+ */
+ if (port->hba->clientcert == clientCertFull && port->hba->auth_method!=uaCert)
+ {
+ ereport(LOG,
+ (errmsg("certificate validation failed for user \"%s\": common name in client certificate does not match user name or mapping, but clientcert=verify-full is enabled.",
+ port->user_name)));
+ }
+ }
+ return status_check_usermap;
}
#endif
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index acf625e4ec..d17682b55a 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1609,7 +1609,7 @@ parse_hba_line(TokenizedLine *tok_line, int elevel)
*/
if (parsedline->auth_method == uaCert)
{
- parsedline->clientcert = true;
+ parsedline->clientcert = clientCertOn;
}
return parsedline;
@@ -1675,11 +1675,16 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
*err_msg = "clientcert can only be configured for \"hostssl\" rows";
return false;
}
- if (strcmp(val, "1") == 0)
+ if (strcmp(val, "1") == 0
+ || strcmp(val, "verify-ca") == 0)
{
- hbaline->clientcert = true;
+ hbaline->clientcert = clientCertOn;
}
- else
+ else if(strcmp(val, "verify-full") == 0)
+ {
+ hbaline->clientcert = clientCertFull;
+ }
+ else if (strcmp(val, "0") == 0)
{
if (hbaline->auth_method == uaCert)
{
@@ -1691,7 +1696,16 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
*err_msg = "clientcert can not be set to 0 when using \"cert\" authentication";
return false;
}
- hbaline->clientcert = false;
+ hbaline->clientcert = clientCertOff;
+ }
+ else
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("invalid value for clientcert: \"%s\"", val),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, HbaFileName)));
+ return false;
}
}
else if (strcmp(name, "pamservice") == 0)
@@ -2250,9 +2264,9 @@ gethba_options(HbaLine *hba)
options[noptions++] =
CStringGetTextDatum(psprintf("map=%s", hba->usermap));
- if (hba->clientcert)
+ if (hba->clientcert != clientCertOff)
options[noptions++] =
- CStringGetTextDatum("clientcert=true");
+ CStringGetTextDatum(psprintf("clientcert=%s", (hba->clientcert == clientCertOn) ? "verify-ca" : "verify-full"));
if (hba->pamservice)
options[noptions++] =
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index 5f68f4c666..ce9a692b8d 100644
--- a/src/include/libpq/hba.h
+++ b/src/include/libpq/hba.h
@@ -58,6 +58,13 @@ typedef enum ConnType
ctHostNoSSL
} ConnType;
+typedef enum ClientCertMode
+{
+ clientCertOff,
+ clientCertOn,
+ clientCertFull
+} ClientCertMode;
+
typedef struct HbaLine
{
int linenumber;
@@ -86,7 +93,7 @@ typedef struct HbaLine
int ldapscope;
char *ldapprefix;
char *ldapsuffix;
- bool clientcert;
+ ClientCertMode clientcert;
char *krb_realm;
bool include_realm;
bool compat_realm;