Thread: Client certificate authentication

Client certificate authentication

From
Magnus Hagander
Date:
Attached patch implements client certificate authentication.

I kept this sitting in my tree without sending it in before the
commitfest because it is entirely dependent on the
not-yet-reviewed-and-applied patch for how to configure client
certificate requesting. But now that I learned how to do it right in
git, breaking it out was very easy :-) Good learning experience.

Anyway. Here it is. Builds on top of the "clientcert option for pg_hba"
patch already on the list.

//Magnus
*** a/doc/src/sgml/client-auth.sgml
--- b/doc/src/sgml/client-auth.sgml
***************
*** 388,393 **** hostnossl  <replaceable>database</replaceable>  <replaceable>user</replaceable>
--- 388,403 ----
         </varlistentry>

         <varlistentry>
+         <term><literal>cert</></term>
+         <listitem>
+          <para>
+           Authenticate using SSL client certificates. See
+           <xref linkend="auth-cert"> for details.
+          </para>
+         </listitem>
+        </varlistentry>
+
+        <varlistentry>
          <term><literal>pam</></term>
          <listitem>
           <para>
***************
*** 1114,1119 **** ldapserver=ldap.example.net prefix="cn=" suffix="dc=example, dc=net"
--- 1124,1148 ----

    </sect2>

+   <sect2 id="auth-cert">
+    <title>Certificate authentication</title>
+
+    <indexterm zone="auth-cert">
+     <primary>Certificate</primary>
+    </indexterm>
+
+    <para>
+     This authentication method uses SSL client certificates to perform
+     authentication. It is therefore only available for SSL connections.
+     When using this authentication method, the server will require that
+     the client provide a certificate. No password prompt will be sent
+     to the client. The <literal>cn</literal> attribute of the certificate
+     will be matched with the username the user is trying to log in as,
+     and if they match the login will be allowed. Username mapping can be
+     used if the usernames don't match.
+    </para>
+   </sect2>
+
    <sect2 id="auth-pam">
     <title>PAM authentication</title>

*** a/doc/src/sgml/runtime.sgml
--- b/doc/src/sgml/runtime.sgml
***************
*** 1674,1684 **** $ <userinput>kill -INT `head -1 /usr/local/pgsql/data/postmaster.pid`</userinput
    </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>

--- 1674,1682 ----
    </para>

    <para>
!    You can use the authentication method <literal>cert</> to use the
!    client certificate for authenticating users. See
!    <xref linkend="auth-cert"> for details.
    </para>
    </sect2>

*** a/src/backend/libpq/auth.c
--- b/src/backend/libpq/auth.c
***************
*** 110,115 **** ULONG(*__ldap_start_tls_sA) (
--- 110,123 ----
  static int    CheckLDAPAuth(Port *port);
  #endif /* USE_LDAP */

+ /*----------------------------------------------------------------
+  * Cert authentication
+  *----------------------------------------------------------------
+  */
+ #ifdef USE_SSL
+ static int    CheckCertAuth(Port *port);
+ #endif
+

  /*----------------------------------------------------------------
   * Kerberos and GSSAPI GUCs
***************
*** 420,425 **** ClientAuthentication(Port *port)
--- 428,441 ----
  #endif
              break;

+         case uaCert:
+ #ifdef USE_SSL
+             status = CheckCertAuth(port);
+ #else
+             Assert(false);
+ #endif
+             break;
+
          case uaTrust:
              status = STATUS_OK;
              break;
***************
*** 2072,2074 **** CheckLDAPAuth(Port *port)
--- 2088,2115 ----
  }
  #endif   /* USE_LDAP */

+
+ /*----------------------------------------------------------------
+  * SSL client certificate authentication
+  *----------------------------------------------------------------
+  */
+ #ifdef USE_SSL
+ static int
+ CheckCertAuth(Port *port)
+ {
+     Assert(port->ssl);
+
+     /* Make sure we have received a username in the certificate */
+     if (port->peer_cn == NULL ||
+         strlen(port->peer_cn) <= 0)
+     {
+         ereport(LOG,
+                 (errmsg("Certificate login failed for user \"%s\": client certificate contains no username",
+                         port->user_name)));
+         return STATUS_ERROR;
+     }
+
+     /* Just pass the certificate CN to the usermap check */
+     return check_usermap(port->hba->usermap, port->user_name, port->peer_cn, false);
+ }
+ #endif
*** a/src/backend/libpq/hba.c
--- b/src/backend/libpq/hba.c
***************
*** 859,864 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
--- 859,870 ----
  #else
          unsupauth = "ldap";
  #endif
+     else if (strcmp(token, "cert") == 0)
+ #ifdef USE_SSL
+         parsedline->auth_method = uaCert;
+ #else
+         unsupauth = "cert";
+ #endif
      else
      {
          ereport(LOG,
***************
*** 893,898 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
--- 899,915 ----
          return false;
      }

+     if (parsedline->conntype != ctHostSSL &&
+         parsedline->auth_method == uaCert)
+     {
+         ereport(LOG,
+                 (errcode(ERRCODE_CONFIG_FILE_ERROR),
+                  errmsg("cert authentication is only supported on hostssl connections"),
+                  errcontext("line %d of configuration file \"%s\"",
+                             line_num, HbaFileName)));
+         return false;
+     }
+
      /* Parse remaining arguments */
      while ((line_item = lnext(line_item)) != NULL)
      {
***************
*** 923,930 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
                  if (parsedline->auth_method != uaIdent &&
                      parsedline->auth_method != uaKrb5 &&
                      parsedline->auth_method != uaGSS &&
!                     parsedline->auth_method != uaSSPI)
!                     INVALID_AUTH_OPTION("map", "ident, krb5, gssapi and sspi");
                  parsedline->usermap = pstrdup(c);
              }
              else if (strcmp(token, "clientcert") == 0)
--- 940,948 ----
                  if (parsedline->auth_method != uaIdent &&
                      parsedline->auth_method != uaKrb5 &&
                      parsedline->auth_method != uaGSS &&
!                     parsedline->auth_method != uaSSPI &&
!                     parsedline->auth_method != uaCert)
!                     INVALID_AUTH_OPTION("map", "ident, krb5, gssapi, sspi and cert");
                  parsedline->usermap = pstrdup(c);
              }
              else if (strcmp(token, "clientcert") == 0)
***************
*** 957,963 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
--- 975,992 ----
                      parsedline->clientcert = true;
                  }
                  else
+                 {
+                     if (parsedline->auth_method == uaCert)
+                     {
+                         ereport(LOG,
+                                 (errcode(ERRCODE_CONFIG_FILE_ERROR),
+                                  errmsg("clientcert can not be set to 0 when using \"cert\" authentication"),
+                                  errcontext("line %d of configuration file \"%s\"",
+                                             line_num, HbaFileName)));
+                         return false;
+                     }
                      parsedline->clientcert = false;
+                 }
              }
              else if (strcmp(token, "pamservice") == 0)
              {
***************
*** 1021,1026 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
--- 1050,1063 ----
      {
          MANDATORY_AUTH_ARG(parsedline->ldapserver, "ldapserver", "ldap");
      }
+
+     /*
+      * Enforce any parameters implied by other settings.
+      */
+     if (parsedline->auth_method == uaCert)
+     {
+         parsedline->clientcert = true;
+     }

      return true;
  }
*** a/src/backend/libpq/pg_hba.conf.sample
--- b/src/backend/libpq/pg_hba.conf.sample
***************
*** 35,41 ****
  # an IP address and netmask in separate columns to specify the set of hosts.
  #
  # METHOD can be "trust", "reject", "md5", "crypt", "password", "gss", "sspi",
! # "krb5", "ident", "pam" or "ldap".  Note that "password" sends passwords
  # in clear text; "md5" is preferred since it sends encrypted passwords.
  #
  # OPTIONS are a set of options for the authentication in the format
--- 35,41 ----
  # an IP address and netmask in separate columns to specify the set of hosts.
  #
  # METHOD can be "trust", "reject", "md5", "crypt", "password", "gss", "sspi",
! # "krb5", "ident", "pam", "ldap" or "cert". Note that "password" sends passwords
  # in clear text; "md5" is preferred since it sends encrypted passwords.
  #
  # OPTIONS are a set of options for the authentication in the format
*** a/src/include/libpq/hba.h
--- b/src/include/libpq/hba.h
***************
*** 26,32 **** typedef enum UserAuth
      uaGSS,
      uaSSPI,
      uaPAM,
!     uaLDAP
  } UserAuth;

  typedef enum ConnType
--- 26,33 ----
      uaGSS,
      uaSSPI,
      uaPAM,
!     uaLDAP,
!     uaCert
  } UserAuth;

  typedef enum ConnType

Re: Client certificate authentication

From
"Alex Hunsaker"
Date:
On Thu, Nov 13, 2008 at 05:31, Magnus Hagander <magnus@hagander.net> wrote:
> Attached patch implements client certificate authentication.
>
> I kept this sitting in my tree without sending it in before the
> commitfest because it is entirely dependent on the
> not-yet-reviewed-and-applied patch for how to configure client
> certificate requesting. But now that I learned how to do it right in
> git, breaking it out was very easy :-) Good learning experience.
>
> Anyway. Here it is. Builds on top of the "clientcert option for pg_hba"
> patch already on the list.

Patch looks good to me and works as described.

Would cncert be a better auth_method name? As later we might have
different types of ssl client cert authentication??

My only concern is there is no way to specify the USER_CERT_FILE for
libpq.  So if for example I have two users that I want to use cert
authentication for I really have to have to users on the system (or i
guess maybe you could fake HOME=... psql -U other_user).   Or am I
missing a way around this? (granted this might be a non-issue for now
as you can use trust clientcert=1 in pg_hba.conf with your other
patch?)


Re: Client certificate authentication

From
Magnus Hagander
Date:
On 16 nov 2008, at 01.00, "Alex Hunsaker" <badalex@gmail.com> wrote:

> On Thu, Nov 13, 2008 at 05:31, Magnus Hagander <magnus@hagander.net>  
> wrote:
>> Attached patch implements client certificate authentication.
>>
>> I kept this sitting in my tree without sending it in before the
>> commitfest because it is entirely dependent on the
>> not-yet-reviewed-and-applied patch for how to configure client
>> certificate requesting. But now that I learned how to do it right in
>> git, breaking it out was very easy :-) Good learning experience.
>>
>> Anyway. Here it is. Builds on top of the "clientcert option for  
>> pg_hba"
>> patch already on the list.
>
> Patch looks good to me and works as described.
>
> Would cncert be a better auth_method name? As later we might have
> different types of ssl client cert authentication??

If/when I'd rather still call it cert, and use an authentication  
option to control which field is matched against.


> My only concern is there is no way to specify the USER_CERT_FILE for
> libpq.  So if for example I have two users that I want to use cert
> authentication for I really have to have to users on the system (or i
> guess maybe you could fake HOME=... psql -U other_user).   Or am I

While not directly related to this patch, that is a very good point.  
We have PGSSLKEY but not PGSSLCERT. Could certainly be worth adding.


>
> missing a way around this? (granted this might be a non-issue for now
> as you can use trust clientcert=1 in pg_hba.conf with your other
> patch?)

Yes, you can use that but the usecase is extremely limited. It only  
works if these are the *only* two users with certificates...

-Magnus


Re: Client certificate authentication

From
Alvaro Herrera
Date:
Magnus Hagander escribió:
> On 16 nov 2008, at 01.00, "Alex Hunsaker" <badalex@gmail.com> wrote:

>> My only concern is there is no way to specify the USER_CERT_FILE for
>> libpq.  So if for example I have two users that I want to use cert
>> authentication for I really have to have to users on the system (or i
>> guess maybe you could fake HOME=... psql -U other_user).   Or am I
>
> While not directly related to this patch, that is a very good point. We 
> have PGSSLKEY but not PGSSLCERT. Could certainly be worth adding.

FWIW I think this was part of the patch submitted by Mark Woodward; see
http://wiki.postgresql.org/wiki/CommitFest_2008-07, and
http://archives.postgresql.org/message-id/20080801203157.GL4321@alvh.no-ip.org

-- 
Alvaro Herrera                                http://www.CommandPrompt.com/
The PostgreSQL Company - Command Prompt, Inc.


Re: Client certificate authentication

From
Magnus Hagander
Date:
Alvaro Herrera wrote:
> Magnus Hagander escribió:
>> On 16 nov 2008, at 01.00, "Alex Hunsaker" <badalex@gmail.com> wrote:
> 
>>> My only concern is there is no way to specify the USER_CERT_FILE for
>>> libpq.  So if for example I have two users that I want to use cert
>>> authentication for I really have to have to users on the system (or i
>>> guess maybe you could fake HOME=... psql -U other_user).   Or am I
>> While not directly related to this patch, that is a very good point. We 
>> have PGSSLKEY but not PGSSLCERT. Could certainly be worth adding.
> 
> FWIW I think this was part of the patch submitted by Mark Woodward; see
> http://wiki.postgresql.org/wiki/CommitFest_2008-07, and
> http://archives.postgresql.org/message-id/20080801203157.GL4321@alvh.no-ip.org

Seems like it. I totally missed that one.

As for the patch itself - do we really want to #ifdef all parameters
out? There's no harm in accepting them for non-ssl connections (and
ignoring them), and that might make life easier on third party stuff
that fills in all parameters with their default values if they're not
specified. Like we support sslmode even if we're compiled without SSL.

And yes, sslkey and PGSSLKEY should be made the same thing, I think.

//Magnus


Re: Client certificate authentication

From
"Alex Hunsaker"
Date:
On Mon, Nov 17, 2008 at 01:01, Magnus Hagander <magnus@hagander.net> wrote:
> On 16 nov 2008, at 01.00, "Alex Hunsaker" <badalex@gmail.com> wrote:
>> Would cncert be a better auth_method name? As later we might have
>> different types of ssl client cert authentication??
>
> If/when I'd rather still call it cert, and use an authentication option to
> control which field is matched against.

Makes sense to me.

FYI I marked this as ready for commiter...


Re: Client certificate authentication

From
"Alex Hunsaker"
Date:
On Mon, Nov 17, 2008 at 05:31, Alvaro Herrera
<alvherre@commandprompt.com> wrote:
> Magnus Hagander escribió:
>> On 16 nov 2008, at 01.00, "Alex Hunsaker" <badalex@gmail.com> wrote:
>
>>> My only concern is there is no way to specify the USER_CERT_FILE for
>>> libpq.  So if for example I have two users that I want to use cert
>>> authentication for I really have to have to users on the system (or i
>>> guess maybe you could fake HOME=... psql -U other_user).   Or am I
>>
>> While not directly related to this patch, that is a very good point. We
>> have PGSSLKEY but not PGSSLCERT. Could certainly be worth adding.
>
> FWIW I think this was part of the patch submitted by Mark Woodward; see
> http://wiki.postgresql.org/wiki/CommitFest_2008-07, and
> http://archives.postgresql.org/message-id/20080801203157.GL4321@alvh.no-ip.org

Cool! I missed this one as well, too bad it does not look like it ever
got resubmitted for this feast :(

> --
> Alvaro Herrera                                http://www.CommandPrompt.com/
> The PostgreSQL Company - Command Prompt, Inc.
>

Re: Client certificate authentication

From
Magnus Hagander
Date:
Alex Hunsaker wrote:
> On Mon, Nov 17, 2008 at 05:31, Alvaro Herrera
> <alvherre@commandprompt.com> wrote:
>> Magnus Hagander escribió:
>>> On 16 nov 2008, at 01.00, "Alex Hunsaker" <badalex@gmail.com> wrote:
>>>> My only concern is there is no way to specify the USER_CERT_FILE for
>>>> libpq.  So if for example I have two users that I want to use cert
>>>> authentication for I really have to have to users on the system (or i
>>>> guess maybe you could fake HOME=... psql -U other_user).   Or am I
>>> While not directly related to this patch, that is a very good point. We
>>> have PGSSLKEY but not PGSSLCERT. Could certainly be worth adding.
>> FWIW I think this was part of the patch submitted by Mark Woodward; see
>> http://wiki.postgresql.org/wiki/CommitFest_2008-07, and
>> http://archives.postgresql.org/message-id/20080801203157.GL4321@alvh.no-ip.org
> 
> Cool! I missed this one as well, too bad it does not look like it ever
> got resubmitted for this feast :(

Actually, isn't that second mail the resubmission? That just didn't go
up on the commitfest page properly?


//Magnus