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;