Re: SSL cleanups/hostname verification - Mailing list pgsql-hackers

From Magnus Hagander
Subject Re: SSL cleanups/hostname verification
Date
Msg-id 491985B1.3090300@hagander.net
Whole thread Raw
In response to Re: SSL cleanups/hostname verification  ("Alex Hunsaker" <badalex@gmail.com>)
Responses Re: SSL cleanups/hostname verification
List pgsql-hackers
Alex Hunsaker wrote:
> On Mon, Oct 20, 2008 at 05:50, Magnus Hagander <magnus@hagander.net> wrote:

> $ SSLVERIFY=cn ./psql junk -h 192.168.0.2
> psql: server common name 'bahdushka' does not match hostname
> '192.168.0.2'FATAL:  no pg_hba.conf entry for host "192.168.0.2", user
> "alex", database "junk", SSL off

It needs to be PGSSLVERIFY if it's an environment variable. sslverify is
the connection parameter.

I think that's confusing your tests all the way through :(

Also, I'd recommend running the server with a log on a different console
or to a file so you don't get client and server error messages mixed up.


> $ SSLVERIFY=none ./psql junk -h bahdushka
> psql: root certificate file (/home/alex/.postgresql/root.crt)

Is that really the whole error message, or was it cut off? Because if it
is, then that is certainly a bug!


> But other than that looks good other than the promised documentation
> and the mem leak Tom Lane noted. (unless I missed an updated patch?)

I think you did, because there is certainly docs in the last one I sent
:-) But here's the very latest-and-greatest - I changed the cn matching
to be case insensitive per offlist comment from Dan Kaminsky, and an
internal return type to bool instead of int.

//Magnus
*** a/doc/src/sgml/libpq.sgml
--- b/doc/src/sgml/libpq.sgml
***************
*** 260,265 ****
--- 260,292 ----
          </varlistentry>

          <varlistentry>
+          <term><literal>sslverify</literal></term>
+          <listitem>
+           <para>
+            This option controls how libpq verifies the certificate on the
+            server when performing an <acronym>SSL</> connection. There are
+            three options: <literal>none</> disables verification completely
+            (not recommended!); <literal>cert</> enables verification that
+            the certificate chains to a known CA only; <literal>cn</> will
+            both verify that the certificate chains to a known CA and that
+            the <literal>cn</> attribute of the certificate matches the
+            hostname the connection is being made to (default).
+           </para>
+
+           <para>
+            It is always recommended to use the <literal>cn</> value for
+            this parameter, since this is the only option that prevents
+            man-in-the-middle attacks. Note that this requires the server
+            name on the certificate to match exactly with the host name
+            used for the connection, and therefore does not support connections
+            to aliased names. It can be used with pure IP address connections
+            only if the certificate also has just the IP address in the
+            <literal>cn</> field.
+           </para>
+          </listitem>
+         </varlistentry>
+
+         <varlistentry>
           <term><literal>requiressl</literal></term>
           <listitem>
            <para>
***************
*** 5682,5687 **** myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
--- 5709,5730 ----
      <listitem>
       <para>
        <indexterm>
+        <primary><envar>PGSSLVERIFY</envar></primary>
+       </indexterm>
+       <envar>PGSSLVERIFY</envar> controls how libpq verifies the certificate on the
+        server when performing an <acronym>SSL</> connection. There are
+        three options: <literal>none</> disables verification completely
+        (not recommended!); <literal>cert</> enables verification that
+        the certificate chains to a known CA only; <literal>cn</> will
+        both verify that the certificate chains to a known CA and that
+        the <literal>cn</> attribute of the certificate matches the
+        hostname the connection is being made to (default).
+      </para>
+     </listitem>
+
+     <listitem>
+      <para>
+       <indexterm>
         <primary><envar>PGREQUIRESSL</envar></primary>
        </indexterm>
        <envar>PGREQUIRESSL</envar> sets whether or not the connection must
***************
*** 6026,6034 **** myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
    </para>

    <para>
!    To verify the server certificate is trustworthy, place certificates of
!    the certificate authorities (<acronym>CA</acronym>) you trust in the
!    file <filename>~/.postgresql/root.crt</> in the user's home directory.
     (On Microsoft Windows the file is named
     <filename>%APPDATA%\postgresql\root.crt</filename>.)
     <application>libpq</application> will then verify that the server's
--- 6069,6079 ----
    </para>

    <para>
!    When the <literal>sslverify</> parameter is set to <literal>cn</> or
!    <literal>cert</>, libpq will verify that the server certificate is
!    trustworthy by checking the certificate chain up to a <acronym>CA</>.
!    For this to work, place the certificate of a trusted <acronym>CA</>
!    in the file <filename>~/.postgresql/root.crt</> in the user's home directory.
     (On Microsoft Windows the file is named
     <filename>%APPDATA%\postgresql\root.crt</filename>.)
     <application>libpq</application> will then verify that the server's
*** a/doc/src/sgml/runtime.sgml
--- b/doc/src/sgml/runtime.sgml
***************
*** 1418,1426 **** $ <userinput>kill -INT `head -1 /usr/local/pgsql/data/postmaster.pid`</userinput
     <filename>server.key</filename> (key) and
     <filename>server.crt</filename> (certificate) files (<xref
     linkend="ssl-tcp">). The TCP client must connect using
!    <literal>sslmode='require'</> (<xref linkend="libpq-connect">) and have
!    a <filename>~/.postgresql/root.crt</> SSL certificate (<xref
!    linkend="libpq-ssl">).
    </para>
   </sect1>

--- 1418,1426 ----
     <filename>server.key</filename> (key) and
     <filename>server.crt</filename> (certificate) files (<xref
     linkend="ssl-tcp">). The TCP client must connect using
!    <literal>sslmode='require'</>, specify <literal>sslverify='cn'</>
!    or <literal>sslverify='cert'</> and have the required certificate
!    files present (<xref linkend="libpq-connect">).
    </para>
   </sect1>

***************
*** 1544,1551 **** $ <userinput>kill -INT `head -1 /usr/local/pgsql/data/postmaster.pid`</userinput

     <listitem>
      <para>
!      It is possible for both the client and server to provide SSL keys
!      or certificates to each other. It takes some extra configuration
       on each side, but this provides stronger verification of identity
       than the mere use of passwords. It prevents a computer from
       pretending to be the server just long enough to read the password
--- 1544,1551 ----

     <listitem>
      <para>
!      It is possible for both the client and server to provide SSL
!      certificates to each other. It takes some extra configuration
       on each side, but this provides stronger verification of identity
       than the mere use of passwords. It prevents a computer from
       pretending to be the server just long enough to read the password
***************
*** 1757,1763 **** chmod og-rwx server.key
      A self-signed certificate can be used for testing, but a certificate
      signed by a certificate authority (<acronym>CA</>) (either one of the
      global <acronym>CAs</> or a local one) should be used in production
!     so the client can verify the server's identity.
     </para>

    </sect2>
--- 1757,1765 ----
      A self-signed certificate can be used for testing, but a certificate
      signed by a certificate authority (<acronym>CA</>) (either one of the
      global <acronym>CAs</> or a local one) should be used in production
!     so the client can verify the server's identity. If all the clients
!     are local to the organization, using a local <acronym>CA</> is
!     recommended.
     </para>

    </sect2>
*** a/src/interfaces/libpq/fe-connect.c
--- b/src/interfaces/libpq/fe-connect.c
***************
*** 92,99 **** static int ldapServiceLookup(const char *purl, PQconninfoOption *options,
--- 92,101 ----
  #define DefaultPassword          ""
  #ifdef USE_SSL
  #define DefaultSSLMode    "prefer"
+ #define DefaultSSLVerify "cn"
  #else
  #define DefaultSSLMode    "disable"
+ #define DefaultSSLVerify "none"
  #endif

  /* ----------
***************
*** 181,186 **** static const PQconninfoOption PQconninfoOptions[] = {
--- 183,191 ----
      {"sslmode", "PGSSLMODE", DefaultSSLMode, NULL,
      "SSL-Mode", "", 8},            /* sizeof("disable") == 8 */

+     {"sslverify", "PGSSLVERIFY", DefaultSSLVerify, NULL,
+     "SSL-Verify", "", 5},        /* sizeof("chain") == 5 */
+
  #if defined(KRB5) || defined(ENABLE_GSS) || defined(ENABLE_SSPI)
      /* Kerberos and GSSAPI authentication support specifying the service name */
      {"krbsrvname", "PGKRBSRVNAME", PG_KRB_SRVNAM, NULL,
***************
*** 415,420 **** connectOptions1(PGconn *conn, const char *conninfo)
--- 420,427 ----
      conn->connect_timeout = tmp ? strdup(tmp) : NULL;
      tmp = conninfo_getval(connOptions, "sslmode");
      conn->sslmode = tmp ? strdup(tmp) : NULL;
+     tmp = conninfo_getval(connOptions, "sslverify");
+     conn->sslverify = tmp ? strdup(tmp) : NULL;
  #ifdef USE_SSL
      tmp = conninfo_getval(connOptions, "requiressl");
      if (tmp && tmp[0] == '1')
***************
*** 530,535 **** connectOptions2(PGconn *conn)
--- 537,560 ----
          conn->sslmode = strdup(DefaultSSLMode);

      /*
+      * Validate sslverify option
+      */
+     if (conn->sslverify)
+     {
+         if (strcmp(conn->sslverify, "none") != 0
+             && strcmp(conn->sslverify, "cert") != 0
+             && strcmp(conn->sslverify, "cn") != 0)
+         {
+             conn->status = CONNECTION_BAD;
+             printfPQExpBuffer(&conn->errorMessage,
+                             libpq_gettext("invalid sslverify value: \"%s\"\n"),
+                               conn->sslverify);
+             return false;
+         }
+     }
+
+
+     /*
       * Only if we get this far is it appropriate to try to connect. (We need a
       * state flag, rather than just the boolean result of this function, in
       * case someone tries to PQreset() the PGconn.)
***************
*** 2008,2013 **** freePGconn(PGconn *conn)
--- 2033,2040 ----
          free(conn->pgpass);
      if (conn->sslmode)
          free(conn->sslmode);
+     if (conn->sslverify)
+         free(conn->sslverify);
  #if defined(KRB5) || defined(ENABLE_GSS) || defined(ENABLE_SSPI)
      if (conn->krbsrvname)
          free(conn->krbsrvname);
*** a/src/interfaces/libpq/fe-secure.c
--- b/src/interfaces/libpq/fe-secure.c
***************
*** 87,95 ****
  #define ERR_pop_to_mark()    ((void) 0)
  #endif

! #ifdef NOT_USED
! static int    verify_peer_name_matches_certificate(PGconn *);
! #endif
  static int    verify_cb(int ok, X509_STORE_CTX *ctx);
  static int    client_cert_cb(SSL *, X509 **, EVP_PKEY **);
  static int    init_ssl_system(PGconn *conn);
--- 87,93 ----
  #define ERR_pop_to_mark()    ((void) 0)
  #endif

! static bool verify_peer_name_matches_certificate(PGconn *);
  static int    verify_cb(int ok, X509_STORE_CTX *ctx);
  static int    client_cert_cb(SSL *, X509 **, EVP_PKEY **);
  static int    init_ssl_system(PGconn *conn);
***************
*** 438,514 **** verify_cb(int ok, X509_STORE_CTX *ctx)
      return ok;
  }

- #ifdef NOT_USED
  /*
   *    Verify that common name resolves to peer.
   */
! static int
  verify_peer_name_matches_certificate(PGconn *conn)
  {
!     struct hostent *cn_hostentry = NULL;
!     struct sockaddr server_addr;
!     struct sockaddr_in *sin (struct sockaddr_in *) &server_addr;
!     ACCEPT_TYPE_ARG3 len;
!     char      **s;
!     unsigned long l;
!
!     /* Get the address on the other side of the socket. */
!     len = sizeof(server_addr);
!     if (getpeername(conn->sock, &server_addr, &len) == -1)
!     {
!         char        sebuf[256];
!
!         printfPQExpBuffer(&conn->errorMessage,
!                           libpq_gettext("error querying socket: %s\n"),
!                           SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
!         return -1;
!     }

!     if (server_addr.sa_family != AF_INET)
      {
          printfPQExpBuffer(&conn->errorMessage,
!                           libpq_gettext("unsupported protocol\n"));
!         return -1;
      }
!
!     /* Get the IP addresses of the certificate's common name (CN) */
      {
-         struct hostent hpstr;
-         char        buf[BUFSIZ];
-         int            herrno = 0;
-
          /*
!          * Currently, pqGethostbyname() is used only on platforms that don't
!          * have getaddrinfo().    If you enable this function, you should
!          * convert the pqGethostbyname() function call to use getaddrinfo().
           */
!         pqGethostbyname(conn->peer_cn, &hpstr, buf, sizeof(buf),
!                         &cn_hostentry, &herrno);
!     }
!
!     /* Did we get an IP address? */
!     if (cn_hostentry == NULL)
!     {
!         printfPQExpBuffer(&conn->errorMessage,
!           libpq_gettext("could not get information about host \"%s\": %s\n"),
!                           conn->peer_cn, hstrerror(h_errno));
!         return -1;
      }
-
-     /* Does one of the CN's IP addresses match the server's IP address? */
-     for (s = cn_hostentry->h_addr_list; *s != NULL; s++)
-         if (!memcmp(&sin->sin_addr.s_addr, *s, cn_hostentry->h_length))
-             return 0;
-
-     l = ntohl(sin->sin_addr.s_addr);
-     printfPQExpBuffer(&conn->errorMessage,
-                       libpq_gettext(
-                         "server common name \"%s\" does not resolve to %ld.%ld.%ld.%ld\n"),
-                       conn->peer_cn, (l >> 24) % 0x100, (l >> 16) % 0x100,
-                       (l >> 8) % 0x100, l % 0x100);
-     return -1;
  }
- #endif   /* NOT_USED */

  /*
   *    Callback used by SSL to load client cert and key.
--- 436,479 ----
      return ok;
  }

  /*
   *    Verify that common name resolves to peer.
   */
! static bool
  verify_peer_name_matches_certificate(PGconn *conn)
  {
!     /*
!      * If told not to verify the peer name, don't do it. Return
!      * 0 indicating that the verification was successful.
!      */
!     if(strcmp(conn->sslverify, "cn") != 0)
!         return true;

!     if (conn->pghostaddr)
      {
          printfPQExpBuffer(&conn->errorMessage,
!                           libpq_gettext("verified SSL connections are only supported when connecting to a
hostname"));
!         return false;
      }
!     else
      {
          /*
!          * Connect by hostname.
!          *
!          * XXX: Should support alternate names here
!          * XXX: Should support wildcard certificates here
           */
!         if (pg_strcasecmp(conn->peer_cn, conn->pghost) != 0)
!         {
!             printfPQExpBuffer(&conn->errorMessage,
!                               libpq_gettext("server common name '%s' does not match hostname '%s'"),
!                               conn->peer_cn, conn->pghost);
!             return false;
!         }
!         else
!             return true;
      }
  }

  /*
   *    Callback used by SSL to load client cert and key.
***************
*** 846,851 **** initialize_SSL(PGconn *conn)
--- 811,822 ----
      if (init_ssl_system(conn))
          return -1;

+     /*
+      * If sslverify is set to anything other than "none", perform certificate
+      * verification. If set to "cn" we will also do further verifications after
+      * the connection has been completed.
+      */
+
      /* Set up to verify server cert, if root.crt is present */
      if (pqGetHomeDirectory(homedir, sizeof(homedir)))
      {
***************
*** 889,894 **** initialize_SSL(PGconn *conn)
--- 860,883 ----

              SSL_CTX_set_verify(SSL_context, SSL_VERIFY_PEER, verify_cb);
          }
+         else
+         {
+             if (strcmp(conn->sslverify, "none") != 0)
+             {
+                 printfPQExpBuffer(&conn->errorMessage,
+                                   libpq_gettext("root certificate file (%s) not found"), fnbuf);
+                 return -1;
+             }
+         }
+     }
+     else
+     {
+         if (strcmp(conn->sslverify, "none") != 0)
+         {
+             printfPQExpBuffer(&conn->errorMessage,
+                               libpq_gettext("cannot find home directory to locate root certificate file"));
+             return -1;
+         }
      }

      /* set up mechanism to provide client certificate, if available */
***************
*** 1004,1016 **** open_client_SSL(PGconn *conn)
                                NID_commonName, conn->peer_cn, SM_USER);
      conn->peer_cn[SM_USER] = '\0';

! #ifdef NOT_USED
!     if (verify_peer_name_matches_certificate(conn) == -1)
      {
          close_SSL(conn);
          return PGRES_POLLING_FAILED;
      }
- #endif

      /* SSL handshake is complete */
      return PGRES_POLLING_OK;
--- 993,1003 ----
                                NID_commonName, conn->peer_cn, SM_USER);
      conn->peer_cn[SM_USER] = '\0';

!     if (!verify_peer_name_matches_certificate(conn))
      {
          close_SSL(conn);
          return PGRES_POLLING_FAILED;
      }

      /* SSL handshake is complete */
      return PGRES_POLLING_OK;
*** a/src/interfaces/libpq/libpq-int.h
--- b/src/interfaces/libpq/libpq-int.h
***************
*** 291,296 **** struct pg_conn
--- 291,297 ----
      char       *pguser;            /* Postgres username and password, if any */
      char       *pgpass;
      char       *sslmode;        /* SSL mode (require,prefer,allow,disable) */
+     char       *sslverify;        /* Verify server SSL certificate (none,chain,cn) */
  #if defined(KRB5) || defined(ENABLE_GSS) || defined(ENABLE_SSPI)
      char       *krbsrvname;        /* Kerberos service name */
  #endif

pgsql-hackers by date:

Previous
From: Magnus Hagander
Date:
Subject: Duplicated docs on libpq parameters
Next
From: Magnus Hagander
Date:
Subject: libpq with ssl vs psql without