Control client certificate requesting with the pg_hba option "clientcert"
authorMagnus Hagander <magnus@hagander.net>
Thu, 20 Nov 2008 09:29:36 +0000 (09:29 +0000)
committerMagnus Hagander <magnus@hagander.net>
Thu, 20 Nov 2008 09:29:36 +0000 (09:29 +0000)
instead of just relying on the root certificate file to be present.

doc/src/sgml/runtime.sgml
src/backend/libpq/auth.c
src/backend/libpq/be-secure.c
src/backend/libpq/hba.c
src/include/libpq/hba.h
src/include/libpq/libpq.h

index ae36597a24e74c1888d33884a09a795dcd268c4c..3b507cd6c56f2de30e366112ea8b19ab5258830e 100644 (file)
@@ -1646,13 +1646,17 @@ $ <userinput>kill -INT `head -1 /usr/local/pgsql/data/postmaster.pid`</userinput
    been entered.
   </para>
 
-  <para>
+  <sect2 id="ssl-client-certificates">
+   <title>Using client certificates</title>
+   <para>
    To require the client to supply a trusted certificate, place
    certificates of the certificate authorities (<acronym>CA</acronym>)
    you trust in the file <filename>root.crt</filename> in the data
-   directory.  A certificate will then be requested from the client during
+   directory, and set the <literal>clientcert</literal> parameter
+   to <literal>1</literal> on the appropriate line(s) in pg_hba.conf.
+   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 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 <filename>root.crl</filename> exists.
@@ -1663,11 +1667,23 @@ $ <userinput>kill -INT `head -1 /usr/local/pgsql/data/postmaster.pid`</userinput
   </para>
 
   <para>
-   If the <filename>root.crt</filename> file is not present, client
-   certificates will not be requested or checked.  In this mode, SSL
-   provides encrypted communication but not authentication.
+   The <literal>clientcert</literal> option in <filename>pg_hba.conf</>
+   is available for all authentication methods, but only for rows
+   specified as <literal>hostssl</>. Unless specified, the default is
+   not to verify the client certificate.
+  </para>
+
+  <para>
+   <productname>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.
   </para>
+  </sect2>
 
+  <sect2 id="ssl-server-files">
+   <title>SSL Server File Usage</title>
   <para>
    The files <filename>server.key</>, <filename>server.crt</>,
    <filename>root.crt</filename>, and <filename>root.crl</filename>
@@ -1704,7 +1720,7 @@ $ <userinput>kill -INT `head -1 /usr/local/pgsql/data/postmaster.pid`</userinput
      <row>
       <entry><filename>root.crt</></entry>
       <entry>trusted certificate authorities</entry>
-      <entry>requests client certificate; checks certificate is
+      <entry>checks that client certificate is
       signed by a trusted certificate authority</entry>
      </row>
 
@@ -1717,6 +1733,7 @@ $ <userinput>kill -INT `head -1 /usr/local/pgsql/data/postmaster.pid`</userinput
     </tbody>
    </tgroup>
   </table>
+  </sect2>
 
   <sect2 id="ssl-certificate-creation">
    <title>Creating a Self-Signed Certificate</title>
index 5c3b39e937fa26550f20f093164a133e7e73c3b4..eaaa7a52622d1df7a160c5a8678ed82889058366 100644 (file)
@@ -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:
index e70958a472f7c8e01a7b5178452c067ae3c03dc3..30b3b798d9444bd2379f7c04f4ec7c6007c72c1e 100644 (file)
@@ -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;
+               }
        }
 }
 
index bf12e21ab7ef970ff6445aa5d4b416c4ff1078c6..f1338bc8c06fa228aa74a4a7f905842e8e6a474f 100644 (file)
@@ -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");
index d9d5cd1950355d5f118209e196df757789c0f94a..eecc50de1e709aa42f9d67a1de7bd32eea9494e3 100644 (file)
@@ -54,6 +54,7 @@ typedef struct
        int                     ldapport;
        char       *ldapprefix;
        char       *ldapsuffix;
+       bool            clientcert;
 } HbaLine;
 
 typedef struct Port hbaPort;
index fdb9555e78feca654a31a2ad2fae8608a10df8a7..c7e7161536bb0800eb51165eaab4e522f1a0274c 100644 (file)
@@ -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);