On Sun, Apr 1, 2018 at 6:07 PM, Magnus Hagander <mag...@hagander.net> wrote:
> On Sun, Apr 1, 2018 at 6:01 PM, Julian Markwort < > julian.markw...@uni-muenster.de> wrote: > >> On 1. of April 2018 17:46:38 MESZ wrote Magnus Hagander < >> mag...@hagander.net>: >> >> >I assume this is a patch that's intended to be applied on top of the >> >previous patch? If so, please submit the complete pach to make sure the >> >correct combination ends up actually being reviewed. >> >> The v02.patch attached to my last mail contains both source and >> documentation changes. >> > > Hmm. I think I may have been looking at the wrong file. Sorry! > > > >Keeping patches as short as possible is not a good thing itself. The >> >important part is that the resulting code and documentation is the best >> >possible. Sometimes you might want to turn it into two patches >> >submitted at >> >the same time if one is clearly just reorganisation, but avoiding code >> >restructure just to keep the lines of patch down is not helpful. >> >> I think I've made the right compromises regarding readability of the >> documentation in my patch then. >> >> A happy Easter, passover, or Sunday to you >> > > You, too! > > (I shall return to reviewing it after the holidays are over) > > I've been through this one again. There is one big omission from it -- it fails to work with the view pg_hba_file_rules. When fixing that, things started to look kind of ugly with the "two booleans to indicate one thing", so I went ahead and changed it to instead be an enum of 3 values. Also, now when using verify-full or verify-ca, I figured its a lot easier to misspell the value, and we don't want that to mean "off". Instead, I made it give an error in this case instead of silently treating it as 0. The option "2" for clientcert was never actually documented, and I think we should get rid of that. We need to keep "1" for backwards compatibility (which arguably was a bad choice originally, but that was many years ago), but let's not add another one. I also made a couple of very small changes to the docs. Attached is an updated patch with these changes. I'd appreciate it if you can run it through your tests to confirm that it didn't break any of those usecases. -- Magnus Hagander Me: https://www.hagander.net/ <http://www.hagander.net/> Work: https://www.redpill-linpro.com/ <http://www.redpill-linpro.com/>
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 <replaceable>database</replaceable> <replaceable>user</replaceable> <para> In addition to the method-specific options listed below, there is one method-independent authentication option <literal>clientcert</literal>, which - can be specified in any <literal>hostssl</literal> record. When set - to <literal>1</literal>, 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 <literal>hostssl</literal> record. + This option can be set to <literal>verify-ca</literal> or + <literal>verify-full</literal>. Both options require the client + to present a valid (trusted) SSL certificate, while + <literal>verify-full</literal> additionally enforces that the + <literal>CN</literal> (Common Name) in the certificate matches + the username or an applicable mapping. + This behaviour is similar to the cert autentication method + (see <xref linkend="auth-cert"/> ) but enables pairing + the verification of client certificates with any authentication + method that supports <literal>hostssl</literal> entries. </para> </listitem> </varlistentry> 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 (<acronym>CA</acronym>s) you trust in a file in the data directory, set the parameter <xref linkend="guc-ssl-ca-file"/> in <filename>postgresql.conf</filename> to the new file name, and add the - authentication option <literal>clientcert=1</literal> to the appropriate + authentication option <literal>clientcert=verify-ca</literal> or + <literal>clientcert=verify-full</literal> to the appropriate <literal>hostssl</literal> line(s) in <filename>pg_hba.conf</filename>. A certificate will then be requested from the client during SSL connection startup. (See <xref linkend="libpq-ssl"/> 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.) + </para> + + <para> + For a <literal>hostssl</literal> entry with + <literal>clientcert=verify-ca</literal>, the server will verify + that the client's certificate is signed by one of the trusted + certificate authorities. If <literal>clientcert=verify-full</literal> + 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 + <literal>cert</literal> authentication method is used + (see <xref linkend="auth-cert"/>). </para> <para> @@ -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. </para> + </sect2> + + <sect2 id="ssl-enforcing-client-certificates"> + <title>Enforcing Client Certificates</title> + <para> + There are two approaches to enforce that users provide a certificate during login. + </para> + + <para> + The first approach makes use of the <literal>cert</literal> authentication + method for <literal>hostssl</literal> entries in <filename>pg_hba.conf</filename>, + such that the certificate itself is used for authentication while also + providing ssl connection security. See <xref linkend="auth-cert"/> for details. + (It is not necessary to specify any <literal>clientcert</literal> + options explicitly when using the <literal>cert</literal> authentication method.) + In this case, the <literal>CN</literal> (nommon name) provided in + the certificate is checked against the user name or an applicable mapping. + </para> <para> - If you are setting up client certificates, you may wish to use - the <literal>cert</literal> authentication method, so that the certificates - control user authentication as well as providing connection security. - See <xref linkend="auth-cert"/> for details. (It is not necessary to - specify <literal>clientcert=1</literal> explicitly when using - the <literal>cert</literal> authentication method.) + The second approach combines any authentication method for <literal>hostssl</literal> + entries with the verification of client certificates by setting the + <literal>clientcert</literal> authentication option to <literal>verify-ca</literal> + or <literal>verify-full</literal>. The former option only enforces that + the certificate is valid, while the latter also ensures that the + <literal>CN</literal> (Common Name) in the certificate matches + the user name or an applicable mapping. </para> </sect2> 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;