From 3c486fbd1c8e8f79902a40ef929c4ed54f122561 Mon Sep 17 00:00:00 2001 From: Magnus Hagander Date: Thu, 20 Nov 2008 09:29:36 +0000 Subject: [PATCH] Control client certificate requesting with the pg_hba option "clientcert" instead of just relying on the root certificate file to be present. --- doc/src/sgml/runtime.sgml | 33 +++++++++++++----- src/backend/libpq/auth.c | 36 +++++++++++++++++++- src/backend/libpq/be-secure.c | 64 ++++++++++++++++++++++++++++------- src/backend/libpq/hba.c | 34 ++++++++++++++++++- src/include/libpq/hba.h | 3 +- src/include/libpq/libpq.h | 3 +- 6 files changed, 148 insertions(+), 25 deletions(-) diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml index b04de288c3..1a862b5c4b 100644 --- a/doc/src/sgml/runtime.sgml +++ b/doc/src/sgml/runtime.sgml @@ -1,4 +1,4 @@ - + Operating System Environment @@ -1646,13 +1646,17 @@ $ kill -INT `head -1 /usr/local/pgsql/data/postmaster.pid` - + + Using client certificates + To require the client to supply a trusted certificate, place certificates of the certificate authorities (CA) you trust in the file root.crt in the data - directory. A certificate will then be requested from the client during + directory, and set the clientcert parameter + to 1 on the appropriate 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 client certificates.) The server will + 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. Certificate Revocation List (CRL) entries are also checked if the file root.crl exists. @@ -1663,11 +1667,23 @@ $ kill -INT `head -1 /usr/local/pgsql/data/postmaster.pid` - If the root.crt file is not present, client - certificates will not be requested or checked. In this mode, SSL - provides encrypted communication but not authentication. + The clientcert option in pg_hba.conf + is available for all authentication methods, but only for rows + specified as hostssl. Unless specified, the default is + not to verify the client certificate. + + PostgreSQL currently does not support authentication + using client certificates, since it cannot differentiate between + different users. As long as the user holds any certificate issued + by a trusted CA it will be accepted, regardless of what account the + user is trying to connect with. + + + + + SSL Server File Usage The files server.key, server.crt, root.crt, and root.crl @@ -1704,7 +1720,7 @@ $ kill -INT `head -1 /usr/local/pgsql/data/postmaster.pid` root.crt trusted certificate authorities - requests client certificate; checks certificate is + checks that client certificate is signed by a trusted certificate authority @@ -1717,6 +1733,7 @@ $ kill -INT `head -1 /usr/local/pgsql/data/postmaster.pid` + Creating a Self-Signed Certificate diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index bccb0a516f..dfa3ff2e9a 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/libpq/auth.c,v 1.171 2008/11/18 13:10:20 petere Exp $ + * $PostgreSQL: pgsql/src/backend/libpq/auth.c,v 1.172 2008/11/20 09:29:36 mha Exp $ * *------------------------------------------------------------------------- */ @@ -275,6 +275,40 @@ ClientAuthentication(Port *port) errmsg("missing or erroneous pg_hba.conf file"), errhint("See server log for details."))); + /* + * This is the first point where we have access to the hba record for + * the current connection, so perform any verifications based on the + * hba options field that should be done *before* the authentication + * here. + */ + if (port->hba->clientcert) + { + /* + * When we parse pg_hba.conf, we have already made sure that we have + * been able to load a certificate store. Thus, if a certificate is + * present on the client, it has been verified against our root + * certificate store, and the connection would have been aborted + * already if it didn't verify ok. + */ +#ifdef USE_SSL + if (!port->peer) + { + ereport(FATAL, + (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), + errmsg("connection requires a valid client certificate"))); + } +#else + /* + * hba.c makes sure hba->clientcert can't be set unless OpenSSL + * is present. + */ + Assert(false); +#endif + } + + /* + * Now proceed to do the actual authentication check + */ switch (port->hba->auth_method) { case uaReject: diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c index bae9fe8464..fd7e703c52 100644 --- a/src/backend/libpq/be-secure.c +++ b/src/backend/libpq/be-secure.c @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/libpq/be-secure.c,v 1.85 2008/10/24 12:24:35 mha Exp $ + * $PostgreSQL: pgsql/src/backend/libpq/be-secure.c,v 1.86 2008/11/20 09:29:36 mha Exp $ * * Since the server static private key ($DataDir/server.key) * will normally be stored unencrypted so that the database @@ -102,6 +102,7 @@ static const char *SSLerrmessage(void); #define RENEGOTIATION_LIMIT (512 * 1024 * 1024) static SSL_CTX *SSL_context = NULL; +static bool ssl_loaded_verify_locations = false; /* GUC variable controlling SSL cipher list */ char *SSLCipherSuites = NULL; @@ -202,6 +203,19 @@ secure_destroy(void) #endif } +/* + * Indicate if we have loaded the root CA store to verify certificates + */ +bool +secure_loaded_verify_locations(void) +{ +#ifdef USE_SSL + return ssl_loaded_verify_locations; +#endif + + return false; +} + /* * Attempt to negotiate secure session. */ @@ -754,15 +768,34 @@ initialize_SSL(void) elog(FATAL, "could not set the cipher list (no valid ciphers available)"); /* - * Require and check client certificates only if we have a root.crt file. + * Attempt to load CA store, so we can verify client certificates if needed. */ - if (!SSL_CTX_load_verify_locations(SSL_context, ROOT_CERT_FILE, NULL)) + if (access(ROOT_CERT_FILE, R_OK)) { - /* Not fatal - we do not require client certificates */ - ereport(LOG, + ssl_loaded_verify_locations = false; + + /* + * If root certificate file simply not found. Don't log an error here, because + * it's quite likely the user isn't planning on using client certificates. + * If we can't access it for other reasons, it is an error. + */ + if (errno != ENOENT) + { + ereport(FATAL, + (errmsg("could not access root certificate file \"%s\": %m", + ROOT_CERT_FILE))); + } + } + else if (!SSL_CTX_load_verify_locations(SSL_context, ROOT_CERT_FILE, NULL)) + { + /* + * File was there, but we could not load it. This means the file is somehow + * broken, and we cannot do verification at all - so abort here. + */ + ssl_loaded_verify_locations = false; + ereport(FATAL, (errmsg("could not load root certificate file \"%s\": %s", - ROOT_CERT_FILE, SSLerrmessage()), - errdetail("Will not verify client certificates."))); + ROOT_CERT_FILE, SSLerrmessage()))); } else { @@ -795,13 +828,18 @@ initialize_SSL(void) ROOT_CRL_FILE, SSLerrmessage()), errdetail("Certificates will not be checked against revocation list."))); } - } - SSL_CTX_set_verify(SSL_context, - (SSL_VERIFY_PEER | - SSL_VERIFY_FAIL_IF_NO_PEER_CERT | - SSL_VERIFY_CLIENT_ONCE), - verify_cb); + /* + * Always ask for SSL client cert, but don't fail if it's not presented. We'll fail later in this case, + * based on what we find in pg_hba.conf. + */ + SSL_CTX_set_verify(SSL_context, + (SSL_VERIFY_PEER | + SSL_VERIFY_CLIENT_ONCE), + verify_cb); + + ssl_loaded_verify_locations = true; + } } } diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index d5e56bda45..64f67818c9 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/libpq/hba.c,v 1.172 2008/10/28 12:10:43 mha Exp $ + * $PostgreSQL: pgsql/src/backend/libpq/hba.c,v 1.173 2008/11/20 09:29:36 mha Exp $ * *------------------------------------------------------------------------- */ @@ -927,6 +927,38 @@ parse_hba_line(List *line, int line_num, HbaLine *parsedline) INVALID_AUTH_OPTION("map", "ident, krb5, gssapi and sspi"); parsedline->usermap = pstrdup(c); } + else if (strcmp(token, "clientcert") == 0) + { + /* + * Since we require ctHostSSL, this really can never happen on non-SSL-enabled + * builds, so don't bother checking for USE_SSL. + */ + if (parsedline->conntype != ctHostSSL) + { + ereport(LOG, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("clientcert can only be configured for \"hostssl\" rows"), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + return false; + } + if (strcmp(c, "1") == 0) + { + if (!secure_loaded_verify_locations()) + { + ereport(LOG, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("client certificates can only be checked if a root certificate store is available"), + errdetail("make sure the root certificate store is present and readable"), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + return false; + } + parsedline->clientcert = true; + } + else + parsedline->clientcert = false; + } else if (strcmp(token, "pamservice") == 0) { REQUIRE_AUTH_OPTION(uaPAM, "pamservice", "pam"); diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h index 79b5a51c6d..cf5942a266 100644 --- a/src/include/libpq/hba.h +++ b/src/include/libpq/hba.h @@ -4,7 +4,7 @@ * Interface to hba.c * * - * $PostgreSQL: pgsql/src/include/libpq/hba.h,v 1.51 2008/10/28 12:10:44 mha Exp $ + * $PostgreSQL: pgsql/src/include/libpq/hba.h,v 1.52 2008/11/20 09:29:36 mha Exp $ * *------------------------------------------------------------------------- */ @@ -54,6 +54,7 @@ typedef struct int ldapport; char *ldapprefix; char *ldapsuffix; + bool clientcert; } HbaLine; typedef struct Port hbaPort; diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h index 53c6437cea..cc4d1bbd7d 100644 --- a/src/include/libpq/libpq.h +++ b/src/include/libpq/libpq.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/libpq/libpq.h,v 1.69 2008/01/01 19:45:58 momjian Exp $ + * $PostgreSQL: pgsql/src/include/libpq/libpq.h,v 1.70 2008/11/20 09:29:36 mha Exp $ * *------------------------------------------------------------------------- */ @@ -67,6 +67,7 @@ extern void pq_endcopyout(bool errorAbort); * prototypes for functions in be-secure.c */ extern int secure_initialize(void); +extern bool secure_loaded_verify_locations(void); extern void secure_destroy(void); extern int secure_open_server(Port *port); extern void secure_close(Port *port);