]> granicus.if.org Git - postgresql/commitdiff
UPDATED PATCH:
authorBruce Momjian <bruce@momjian.us>
Fri, 14 Jun 2002 04:23:17 +0000 (04:23 +0000)
committerBruce Momjian <bruce@momjian.us>
Fri, 14 Jun 2002 04:23:17 +0000 (04:23 +0000)
Attached are a revised set of SSL patches.  Many of these patches
are motivated by security concerns, it's not just bug fixes.  The key
differences (from stock 7.2.1) are:

*) almost all code that directly uses the OpenSSL library is in two
   new files,

     src/interfaces/libpq/fe-ssl.c
     src/backend/postmaster/be-ssl.c

   in the long run, it would be nice to merge these two files.

*) the legacy code to read and write network data have been
   encapsulated into read_SSL() and write_SSL().  These functions
   should probably be renamed - they handle both SSL and non-SSL
   cases.

   the remaining code should eliminate the problems identified
   earlier, albeit not very cleanly.

*) both front- and back-ends will send a SSL shutdown via the
   new close_SSL() function.  This is necessary for sessions to
   work properly.

   (Sessions are not yet fully supported, but by cleanly closing
   the SSL connection instead of just sending a TCP FIN packet
   other SSL tools will be much happier.)

*) The client certificate and key are now expected in a subdirectory
   of the user's home directory.  Specifically,

- the directory .postgresql must be owned by the user, and
  allow no access by 'group' or 'other.'

- the file .postgresql/postgresql.crt must be a regular file
  owned by the user.

- the file .postgresql/postgresql.key must be a regular file
  owned by the user, and allow no access by 'group' or 'other'.

   At the current time encrypted private keys are not supported.
   There should also be a way to support multiple client certs/keys.

*) the front-end performs minimal validation of the back-end cert.
   Self-signed certs are permitted, but the common name *must*
   match the hostname used by the front-end.  (The cert itself
   should always use a fully qualified domain name (FDQN) in its
   common name field.)

   This means that

  psql -h eris db

   will fail, but

  psql -h eris.example.com db

   will succeed.  At the current time this must be an exact match;
   future patches may support any FQDN that resolves to the address
   returned by getpeername(2).

   Another common "problem" is expiring certs.  For now, it may be
   a good idea to use a very-long-lived self-signed cert.

   As a compile-time option, the front-end can specify a file
   containing valid root certificates, but it is not yet required.

*) the back-end performs minimal validation of the client cert.
   It allows self-signed certs.  It checks for expiration.  It
   supports a compile-time option specifying a file containing
   valid root certificates.

*) both front- and back-ends default to TLSv1, not SSLv3/SSLv2.

*) both front- and back-ends support DSA keys.  DSA keys are
   moderately more expensive on startup, but many people consider
   them preferable than RSA keys.  (E.g., SSH2 prefers DSA keys.)

*) if /dev/urandom exists, both client and server will read 16k
   of randomization data from it.

*) the server can read empheral DH parameters from the files

     $DataDir/dh512.pem
     $DataDir/dh1024.pem
     $DataDir/dh2048.pem
     $DataDir/dh4096.pem

   if none are provided, the server will default to hardcoded
   parameter files provided by the OpenSSL project.

Remaining tasks:

*) the select() clauses need to be revisited - the SSL abstraction
   layer may need to absorb more of the current code to avoid rare
   deadlock conditions.  This also touches on a true solution to
   the pg_eof() problem.

*) the SIGPIPE signal handler may need to be revisited.

*) support encrypted private keys.

*) sessions are not yet fully supported.  (SSL sessions can span
   multiple "connections," and allow the client and server to avoid
   costly renegotiations.)

*) makecert - a script that creates back-end certs.

*) pgkeygen - a tool that creates front-end certs.

*) the whole protocol issue, SASL, etc.

 *) certs are fully validated - valid root certs must be available.
    This is a hassle, but it means that you *can* trust the identity
    of the server.

 *) the client library can handle hardcoded root certificates, to
    avoid the need to copy these files.

 *) host name of server cert must resolve to IP address, or be a
    recognized alias.  This is more liberal than the previous
    iteration.

 *) the number of bytes transferred is tracked, and the session
    key is periodically renegotiated.

 *) basic cert generation scripts (mkcert.sh, pgkeygen.sh).  The
    configuration files have reasonable defaults for each type
    of use.

Bear Giles

src/backend/libpq/Makefile
src/backend/libpq/be-secure.c [new file with mode: 0644]
src/backend/libpq/pqcomm.c
src/backend/postmaster/postmaster.c
src/interfaces/libpq/Makefile
src/interfaces/libpq/fe-connect.c
src/interfaces/libpq/fe-misc.c
src/interfaces/libpq/fe-secure.c [new file with mode: 0644]
src/interfaces/libpq/libpq-int.h

index 87f00e68ce6e7e73555ac7eef8a42bfd576e4927..cc4f750a7d68ba1e932bb36d47323449cede2808 100644 (file)
@@ -4,7 +4,7 @@
 #    Makefile for libpq subsystem (backend half of libpq interface)
 #
 # IDENTIFICATION
-#    $Header: /cvsroot/pgsql/src/backend/libpq/Makefile,v 1.32 2002/06/14 04:09:36 momjian Exp $
+#    $Header: /cvsroot/pgsql/src/backend/libpq/Makefile,v 1.33 2002/06/14 04:23:17 momjian Exp $
 #
 #-------------------------------------------------------------------------
 
@@ -14,7 +14,8 @@ include $(top_builddir)/src/Makefile.global
 
 # be-fsstubs is here for historical reasons, probably belongs elsewhere
 
-OBJS = be-fsstubs.o auth.o crypt.o hba.o md5.o pqcomm.o pqformat.o pqsignal.o
+OBJS = be-fsstubs.o be-secure.o auth.o crypt.o hba.o md5.o pqcomm.o \
+       pqformat.o pqsignal.o
 
 
 all: SUBSYS.o
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
new file mode 100644 (file)
index 0000000..6501882
--- /dev/null
@@ -0,0 +1,365 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-connect.c
+ *       functions related to setting up a secure connection to the frontend.
+ *       Secure connections are expected to provide confidentiality,
+ *       message integrity and endpoint authentication.
+ *
+ *
+ * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *       $Header: /cvsroot/pgsql/src/backend/libpq/be-secure.c,v 1.1 2002/06/14 04:23:17 momjian Exp $
+ *       
+ * PATCH LEVEL
+ *       milestone 1: fix basic coding errors
+ *       [*] existing SSL code pulled out of existing files.
+ *       [*] SSL_get_error() after SSL_read() and SSL_write(),
+ *           SSL_shutdown(), default to TLSv1.
+ *     
+ *       milestone 2: provide endpoint authentication (server)
+ *       [*] client verifies server cert
+ *       [*] client verifies server hostname
+ *
+ *       milestone 3: improve confidentially, support perfect forward secrecy
+ *       [ ] use 'random' file, read from '/dev/urandom?'
+ *       [ ] emphermal DH keys, default values
+ *       [ ] periodic renegotiation
+ *
+ *       milestone 4: provide endpoint authentication (client)
+ *       [ ] server verifies client certificates
+ *
+ *       milestone 5: provide informational callbacks
+ *       [ ] provide informational callbacks
+ *
+ *       other changes
+ *       [ ] tcp-wrappers
+ *       [ ] more informative psql
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/types.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include "libpq/libpq.h"
+#include "libpq/pqsignal.h"
+#include "miscadmin.h"
+
+#ifdef WIN32
+#include "win32.h"
+#else
+#include <sys/socket.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#ifdef HAVE_NETINET_TCP_H
+#include <netinet/tcp.h>
+#endif
+#include <arpa/inet.h>
+#endif
+
+
+#ifndef HAVE_STRDUP
+#include "strdup.h"
+#endif
+
+#ifdef USE_SSL
+#include <openssl/ssl.h>
+#include <openssl/e_os.h>
+#endif
+
+extern void ExitPostmaster(int);
+extern void postmaster_error(const char *fmt,...);
+
+int secure_initialize(void);
+void secure_destroy(void);
+int secure_open_server(Port *);
+void secure_close(Port *);
+ssize_t secure_read(Port *, void *ptr, size_t len);
+ssize_t secure_write(Port *, const void *ptr, size_t len);
+
+#ifdef USE_SSL
+static int initialize_SSL(void);
+static void destroy_SSL(void);
+static int open_server_SSL(Port *);
+static void close_SSL(Port *);
+static const char *SSLerrmessage(void);
+#endif
+
+#ifdef USE_SSL
+static SSL_CTX *SSL_context = NULL;
+#endif
+
+/* ------------------------------------------------------------ */
+/*           Procedures common to all secure sessions           */
+/* ------------------------------------------------------------ */
+
+/*
+ *     Initialize global context
+ */
+int
+secure_initialize (void)
+{
+       int r = 0;
+
+#ifdef USE_SSL
+       r = initialize_SSL();
+#endif
+
+       return r;
+}
+
+/*
+ *     Destroy global context
+ */
+void
+secure_destroy (void)
+{
+#ifdef USE_SSL
+       destroy_SSL();
+#endif
+}
+
+/*
+ *     Attempt to negotiate secure session.
+ */
+int 
+secure_open_server (Port *port)
+{
+       int r = 0;
+
+#ifdef USE_SSL
+       r = open_server_SSL(port);
+#endif
+
+       return r;
+}
+
+/*
+ *     Close secure session.
+ */
+void
+secure_close (Port *port)
+{
+#ifdef USE_SSL
+       if (port->ssl)
+               close_SSL(port);
+#endif
+}
+
+/*
+ *     Read data from a secure connection.
+ */
+ssize_t
+secure_read (Port *port, void *ptr, size_t len)
+{
+       ssize_t n;
+
+#ifdef USE_SSL
+       if (port->ssl)
+       {
+               n = SSL_read(port->ssl, ptr, len);
+               switch (SSL_get_error(port->ssl, n))
+               {
+               case SSL_ERROR_NONE:
+                       break;
+               case SSL_ERROR_WANT_READ:
+                       break;
+               case SSL_ERROR_SYSCALL:
+                       errno = get_last_socket_error();
+                       elog(ERROR, "SSL SYSCALL error: %s", strerror(errno));
+                       break;
+               case SSL_ERROR_SSL:
+                       elog(ERROR, "SSL error: %s", SSLerrmessage());
+                       /* fall through */
+               case SSL_ERROR_ZERO_RETURN:
+                       secure_close(port);
+                       errno = ECONNRESET;
+                       n = -1;
+                       break;
+               }
+       }
+       else
+#endif
+       n = recv(port->sock, ptr, len, 0);
+
+       return n;
+}
+
+/*
+ *     Write data to a secure connection.
+ */
+ssize_t
+secure_write (Port *port, const void *ptr, size_t len)
+{
+       ssize_t n;
+
+#ifndef WIN32
+       pqsigfunc oldsighandler = pqsignal(SIGPIPE, SIG_IGN);
+#endif
+
+#ifdef USE_SSL
+       if (port->ssl)
+       {
+               n = SSL_write(port->ssl, ptr, len);
+               switch (SSL_get_error(port->ssl, n))
+               {
+               case SSL_ERROR_NONE:
+                       break;
+               case SSL_ERROR_WANT_WRITE:
+                       break;
+               case SSL_ERROR_SYSCALL:
+                       errno = get_last_socket_error();
+                       elog(ERROR, "SSL SYSCALL error: %s", strerror(errno));
+                       break;
+               case SSL_ERROR_SSL:
+                       elog(ERROR, "SSL error: %s", SSLerrmessage());
+                       /* fall through */
+               case SSL_ERROR_ZERO_RETURN:
+                       secure_close(port);
+                       errno = ECONNRESET;
+                       n = -1;
+                       break;
+               }
+       }
+       else
+#endif
+       n = send(port->sock, ptr, len, 0);
+
+#ifndef WIN32
+       pqsignal(SIGPIPE, oldsighandler);
+#endif
+
+       return n;
+}
+
+/* ------------------------------------------------------------ */
+/*                        SSL specific code                     */
+/* ------------------------------------------------------------ */
+#ifdef USE_SSL
+/*
+ *     Initialize global SSL context.
+ */
+static int
+initialize_SSL (void)
+{
+       char fnbuf[2048];
+
+       if (!SSL_context)
+       {
+               SSL_library_init();
+               SSL_load_error_strings();
+               SSL_context = SSL_CTX_new(TLSv1_method());
+               if (!SSL_context)
+               {
+                       postmaster_error("failed to create SSL context: %s",
+                                                        SSLerrmessage());
+                       ExitPostmaster(1);
+               }
+
+               /*
+                *      Load and verify certificate and private key
+                */
+               snprintf(fnbuf, sizeof(fnbuf), "%s/server.crt", DataDir);
+               if (!SSL_CTX_use_certificate_file(SSL_context, fnbuf, SSL_FILETYPE_PEM))
+               {
+                       postmaster_error("failed to load server certificate (%s): %s",
+                                                        fnbuf, SSLerrmessage());
+                       ExitPostmaster(1);
+               }
+               snprintf(fnbuf, sizeof(fnbuf), "%s/server.key", DataDir);
+               if (!SSL_CTX_use_PrivateKey_file(SSL_context, fnbuf, SSL_FILETYPE_PEM))
+               {
+                       postmaster_error("failed to load private key file (%s): %s",
+                                                        fnbuf, SSLerrmessage());
+                       ExitPostmaster(1);
+               }
+               if (!SSL_CTX_check_private_key(SSL_context))
+               {
+                       postmaster_error("check of private key failed: %s",
+                                                        SSLerrmessage());
+                       ExitPostmaster(1);
+               }
+       }
+
+       return 0;
+}
+
+/*
+ *     Destroy global SSL context.
+ */
+static void
+destroy_SSL (void)
+{
+       if (SSL_context)
+       {
+               SSL_CTX_free(SSL_context);
+               SSL_context = NULL;
+       }
+}
+
+/*
+ *     Attempt to negotiate SSL connection.
+ */
+static int
+open_server_SSL (Port *port)
+{
+       if (!(port->ssl = SSL_new(SSL_context)) ||
+               !SSL_set_fd(port->ssl, port->sock) ||
+               SSL_accept(port->ssl) <= 0)
+       {
+               elog(ERROR, "failed to initialize SSL connection: %s", SSLerrmessage());
+               close_SSL(port);
+               return -1;
+       }
+
+       return 0;
+}
+
+/*
+ *     Close SSL connection.
+ */
+static void
+close_SSL (Port *port)
+{
+       if (port->ssl)
+       {
+               SSL_shutdown(port->ssl);
+               SSL_free(port->ssl);
+               port->ssl = NULL;
+       }
+}
+
+/*
+ * Obtain reason string for last SSL error
+ *
+ * Some caution is needed here since ERR_reason_error_string will
+ * return NULL if it doesn't recognize the error code.  We don't
+ * want to return NULL ever.
+ */
+static const char *
+SSLerrmessage(void)
+{
+       unsigned long   errcode;
+       const char         *errreason;
+       static char             errbuf[32];
+
+       errcode = ERR_get_error();
+       if (errcode == 0)
+               return "No SSL error reported";
+       errreason = ERR_reason_error_string(errcode);
+       if (errreason != NULL)
+               return errreason;
+       snprintf(errbuf, sizeof(errbuf), "SSL error code %lu", errcode);
+       return errbuf;
+}
+
+#endif /* USE_SSL */
index 4a71eb1615579d5a3ebf57bf3a0ce63a8c9d9329..ace32190c3f668ef61308c93976ba761e862d32e 100644 (file)
@@ -29,7 +29,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- *     $Id: pqcomm.c,v 1.135 2002/06/14 04:09:36 momjian Exp $
+ *     $Id: pqcomm.c,v 1.136 2002/06/14 04:23:17 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -81,6 +81,9 @@
 #include "miscadmin.h"
 #include "storage/ipc.h"
 
+extern void secure_close(Port *);
+extern ssize_t secure_read(Port *, void *, size_t);
+extern ssize_t secure_write(Port *, const void *, size_t);
 
 static void pq_close(void);
 
@@ -138,6 +141,7 @@ pq_close(void)
 {
        if (MyProcPort != NULL)
        {
+               secure_close(MyProcPort);
                close(MyProcPort->sock);
                /* make sure any subsequent attempts to do I/O fail cleanly */
                MyProcPort->sock = -1;
@@ -457,14 +461,8 @@ pq_recvbuf(void)
        {
                int                     r;
 
-#ifdef USE_SSL
-               if (MyProcPort->ssl)
-                       r = SSL_read(MyProcPort->ssl, PqRecvBuffer + PqRecvLength,
-                                                PQ_BUFFER_SIZE - PqRecvLength);
-               else
-#endif
-                       r = recv(MyProcPort->sock, PqRecvBuffer + PqRecvLength,
-                                        PQ_BUFFER_SIZE - PqRecvLength, 0);
+               r = secure_read(MyProcPort, PqRecvBuffer + PqRecvLength,
+                                               PQ_BUFFER_SIZE - PqRecvLength);
 
                if (r < 0)
                {
@@ -651,12 +649,7 @@ pq_flush(void)
        {
                int                     r;
 
-#ifdef USE_SSL
-               if (MyProcPort->ssl)
-                       r = SSL_write(MyProcPort->ssl, bufptr, bufend - bufptr);
-               else
-#endif
-                       r = send(MyProcPort->sock, bufptr, bufend - bufptr, 0);
+               r = secure_write(MyProcPort, bufptr, bufend - bufptr);
 
                if (r <= 0)
                {
index 12b09411812cd2cf70dee4f8f90c0c1bbf0ac105..8a3b45022f412e3daceb79b7a35a7c4ca23c12f1 100644 (file)
@@ -37,7 +37,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/postmaster/postmaster.c,v 1.278 2002/06/14 04:09:36 momjian Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/postmaster/postmaster.c,v 1.279 2002/06/14 04:23:17 momjian Exp $
  *
  * NOTES
  *
@@ -165,10 +165,6 @@ static int ServerSock_INET = INVALID_SOCK;         /* stream socket server */
 static int     ServerSock_UNIX = INVALID_SOCK;         /* stream socket server */
 #endif
 
-#ifdef USE_SSL
-static SSL_CTX *SSL_context = NULL;            /* Global SSL context */
-#endif
-
 /*
  * Set by the -o option
  */
@@ -245,7 +241,7 @@ static void CleanupProc(int pid, int exitstatus);
 static void LogChildExit(int lev, const char *procname,
                                                 int pid, int exitstatus);
 static int     DoBackend(Port *port);
-static void ExitPostmaster(int status);
+       void ExitPostmaster(int status);
 static void usage(const char *);
 static int     ServerLoop(void);
 static int     BackendStartup(Port *port);
@@ -264,7 +260,7 @@ static void SignalChildren(int signal);
 static int     CountChildren(void);
 static bool CreateOptsFile(int argc, char *argv[]);
 static pid_t SSDataBase(int xlop);
-static void
+       void
 postmaster_error(const char *fmt,...)
 /* This lets gcc check the format string for consistency. */
 __attribute__((format(printf, 1, 2)));
@@ -274,9 +270,11 @@ __attribute__((format(printf, 1, 2)));
 #define ShutdownDataBase()             SSDataBase(BS_XLOG_SHUTDOWN)
 
 #ifdef USE_SSL
-static void InitSSL(void);
-static const char *SSLerrmessage(void);
-#endif
+extern int secure_initialize(void);
+extern void secure_destroy(void);
+extern int secure_open_server(Port *);
+extern void secure_close(Port *);
+#endif /* USE_SSL */
 
 
 static void
@@ -609,7 +607,7 @@ PostmasterMain(int argc, char *argv[])
                ExitPostmaster(1);
        }
        if (EnableSSL)
-               InitSSL();
+               secure_initialize();
 #endif
 
        /*
@@ -1113,17 +1111,8 @@ ProcessStartupPacket(Port *port, bool SSLdone)
                }
 
 #ifdef USE_SSL
-               if (SSLok == 'S')
-               {
-                       if (!(port->ssl = SSL_new(SSL_context)) ||
-                               !SSL_set_fd(port->ssl, port->sock) ||
-                               SSL_accept(port->ssl) <= 0)
-                       {
-                               elog(LOG, "failed to initialize SSL connection: %s (%m)",
-                                        SSLerrmessage());
+               if (SSLok == 'S' && secure_open_server(port) == -1)
                                return STATUS_ERROR;
-                       }
-               }
 #endif
                /* regular startup packet, cancel, etc packet should follow... */
                /* but not another SSL negotiation request */
@@ -1322,8 +1311,7 @@ static void
 ConnFree(Port *conn)
 {
 #ifdef USE_SSL
-       if (conn->ssl)
-               SSL_free(conn->ssl);
+       secure_close(conn);
 #endif
        free(conn);
 }
@@ -2246,7 +2234,7 @@ DoBackend(Port *port)
  *
  * Do NOT call exit() directly --- always go through here!
  */
-static void
+void
 ExitPostmaster(int status)
 {
        /* should cleanup shared memory and kill all backends */
@@ -2424,73 +2412,6 @@ CountChildren(void)
        return cnt;
 }
 
-#ifdef USE_SSL
-
-/*
- * Initialize SSL library and structures
- */
-static void
-InitSSL(void)
-{
-       char            fnbuf[2048];
-
-       SSL_load_error_strings();
-       SSL_library_init();
-       SSL_context = SSL_CTX_new(SSLv23_method());
-       if (!SSL_context)
-       {
-               postmaster_error("failed to create SSL context: %s",
-                                                SSLerrmessage());
-               ExitPostmaster(1);
-       }
-       snprintf(fnbuf, sizeof(fnbuf), "%s/server.crt", DataDir);
-       if (!SSL_CTX_use_certificate_file(SSL_context, fnbuf, SSL_FILETYPE_PEM))
-       {
-               postmaster_error("failed to load server certificate (%s): %s",
-                                                fnbuf, SSLerrmessage());
-               ExitPostmaster(1);
-       }
-       snprintf(fnbuf, sizeof(fnbuf), "%s/server.key", DataDir);
-       if (!SSL_CTX_use_PrivateKey_file(SSL_context, fnbuf, SSL_FILETYPE_PEM))
-       {
-               postmaster_error("failed to load private key file (%s): %s",
-                                                fnbuf, SSLerrmessage());
-               ExitPostmaster(1);
-       }
-       if (!SSL_CTX_check_private_key(SSL_context))
-       {
-               postmaster_error("check of private key failed: %s",
-                                                SSLerrmessage());
-               ExitPostmaster(1);
-       }
-}
-
-/*
- * Obtain reason string for last SSL error
- *
- * Some caution is needed here since ERR_reason_error_string will
- * return NULL if it doesn't recognize the error code.  We don't
- * want to return NULL ever.
- */
-static const char *
-SSLerrmessage(void)
-{
-       unsigned long   errcode;
-       const char         *errreason;
-       static char             errbuf[32];
-
-       errcode = ERR_get_error();
-       if (errcode == 0)
-               return "No SSL error reported";
-       errreason = ERR_reason_error_string(errcode);
-       if (errreason != NULL)
-               return errreason;
-       snprintf(errbuf, sizeof(errbuf), "SSL error code %lu", errcode);
-       return errbuf;
-}
-
-#endif /* USE_SSL */
-
 /*
  * Fire off a subprocess for startup/shutdown/checkpoint.
  *
@@ -2693,7 +2614,7 @@ CreateOptsFile(int argc, char *argv[])
  * This should be used only for reporting "interactive" errors (ie, errors
  * during startup.  Once the postmaster is launched, use elog.
  */
-static void
+void
 postmaster_error(const char *fmt,...)
 {
        va_list         ap;
index e98be1b8a6f4b4189168306fe6ae9e670f67fc38..e0adb54b93a5365886aa427cd620bdb0fb0f67e1 100644 (file)
@@ -4,7 +4,7 @@
 #
 # Copyright (c) 1994, Regents of the University of California
 #
-# $Header: /cvsroot/pgsql/src/interfaces/libpq/Makefile,v 1.61 2002/06/14 04:09:37 momjian Exp $
+# $Header: /cvsroot/pgsql/src/interfaces/libpq/Makefile,v 1.62 2002/06/14 04:23:17 momjian Exp $
 #
 #-------------------------------------------------------------------------
 
@@ -20,7 +20,7 @@ SO_MINOR_VERSION= 2
 override CPPFLAGS := -I$(srcdir) $(CPPFLAGS) -DFRONTEND -DSYSCONFDIR='"$(sysconfdir)"'
 
 OBJS= fe-auth.o fe-connect.o fe-exec.o fe-misc.o fe-print.o fe-lobj.o \
-      pqexpbuffer.o dllist.o md5.o pqsignal.o \
+      pqexpbuffer.o dllist.o md5.o pqsignal.o fe-secure.o \
       $(INET_ATON) $(SNPRINTF) $(STRERROR)
 
 ifdef MULTIBYTE
index 02164778b76a400dd7ca8504e0ea1930f189e01f..414fff329e58076c206ffafcbb4fc6972bdfaf31 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v 1.185 2002/06/14 04:09:37 momjian Exp $
+ *       $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v 1.186 2002/06/14 04:23:17 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -61,9 +61,12 @@ inet_aton(const char *cp, struct in_addr * inp)
 }
 #endif
 
-
 #ifdef USE_SSL
-static SSL_CTX *SSL_context = NULL;
+extern int secure_initialize(PGconn *);
+extern void secure_destroy(void);
+extern int secure_open_client(PGconn *);
+extern void secure_close(PGconn *);
+extern SSL * PQgetssl(PGconn *);
 #endif
 
 #define NOTIFYLIST_INITIAL_SIZE 10
@@ -186,10 +189,6 @@ static char *conninfo_getval(PQconninfoOption *connOptions,
 static void defaultNoticeProcessor(void *arg, const char *message);
 static int parseServiceInfo(PQconninfoOption *options,
                                 PQExpBuffer errorMessage);
-#ifdef USE_SSL
-static const char *SSLerrmessage(void);
-#endif
-
 
 /*
  *             Connecting to a Database
@@ -969,26 +968,8 @@ retry2:
                }
                if (SSLok == 'S')
                {
-                       if (!SSL_context)
+                       if (secure_initialize(conn) == -1 || secure_open_client(conn) == -1)
                        {
-                               SSL_load_error_strings();
-                               SSL_library_init();
-                               SSL_context = SSL_CTX_new(SSLv23_method());
-                               if (!SSL_context)
-                               {
-                                       printfPQExpBuffer(&conn->errorMessage,
-                                        libpq_gettext("could not create SSL context: %s\n"),
-                                                                         SSLerrmessage());
-                                       goto connect_errReturn;
-                               }
-                       }
-                       if (!(conn->ssl = SSL_new(SSL_context)) ||
-                               !SSL_set_fd(conn->ssl, conn->sock) ||
-                               SSL_connect(conn->ssl) <= 0)
-                       {
-                               printfPQExpBuffer(&conn->errorMessage,
-                               libpq_gettext("could not establish SSL connection: %s\n"),
-                                                                 SSLerrmessage());
                                goto connect_errReturn;
                        }
                        /* SSL connection finished. Continue to send startup packet */
@@ -998,6 +979,7 @@ retry2:
                        /* Received error - probably protocol mismatch */
                        if (conn->Pfdebug)
                                fprintf(conn->Pfdebug, "Postmaster reports error, attempting fallback to pre-7.0.\n");
+                       secure_close(conn);
 #ifdef WIN32
                        closesocket(conn->sock);
 #else
@@ -1039,6 +1021,7 @@ retry2:
 connect_errReturn:
        if (conn->sock >= 0)
        {
+               secure_close(conn);
 #ifdef WIN32
                closesocket(conn->sock);
 #else
@@ -1914,8 +1897,7 @@ freePGconn(PGconn *conn)
                return;
        pqClearAsyncResult(conn);       /* deallocate result and curTuple */
 #ifdef USE_SSL
-       if (conn->ssl)
-               SSL_free(conn->ssl);
+       secure_close(conn);
 #endif
        if (conn->sock >= 0)
        {
@@ -1992,6 +1974,7 @@ closePGconn(PGconn *conn)
         */
        if (conn->sock >= 0)
        {
+               secure_close(conn);
 #ifdef WIN32
                closesocket(conn->sock);
 #else
@@ -2641,35 +2624,6 @@ PQconninfoFree(PQconninfoOption *connOptions)
 }
 
 
-#ifdef USE_SSL
-
-/*
- * Obtain reason string for last SSL error
- *
- * Some caution is needed here since ERR_reason_error_string will
- * return NULL if it doesn't recognize the error code.  We don't
- * want to return NULL ever.
- */
-static const char *
-SSLerrmessage(void)
-{
-       unsigned long   errcode;
-       const char         *errreason;
-       static char             errbuf[32];
-
-       errcode = ERR_get_error();
-       if (errcode == 0)
-               return "No SSL error reported";
-       errreason = ERR_reason_error_string(errcode);
-       if (errreason != NULL)
-               return errreason;
-       snprintf(errbuf, sizeof(errbuf), "SSL error code %lu", errcode);
-       return errbuf;
-}
-
-#endif /* USE_SSL */
-
-
 /* =========== accessor functions for PGconn ========= */
 char *
 PQdb(const PGconn *conn)
@@ -2814,16 +2768,6 @@ PQsetClientEncoding(PGconn *conn, const char *encoding)
 }
 #endif
 
-#ifdef USE_SSL
-SSL *
-PQgetssl(PGconn *conn)
-{
-       if (!conn)
-               return NULL;
-       return conn->ssl;
-}
-#endif
-
 void
 PQtrace(PGconn *conn, FILE *debug_port)
 {
index a8af4d893ab78b8a980daeff4cac49ebf410823a..15b6dcb1e53803e87c2ba68861ace55544a76695 100644 (file)
@@ -25,7 +25,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-misc.c,v 1.72 2002/06/14 04:09:37 momjian Exp $
+ *       $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-misc.c,v 1.73 2002/06/14 04:23:17 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -55,6 +55,9 @@
 #include "mb/pg_wchar.h"
 #endif
 
+extern void secure_close(PGconn *);
+extern ssize_t secure_read(PGconn *, void *, size_t);
+extern ssize_t secure_write(PGconn *, const void *, size_t);
 
 #define DONOTICE(conn,message) \
        ((*(conn)->noticeHook) ((conn)->noticeArg, (message)))
@@ -477,14 +480,8 @@ pqReadData(PGconn *conn)
 
        /* OK, try to read some data */
 retry3:
-#ifdef USE_SSL
-       if (conn->ssl)
-               nread = SSL_read(conn->ssl, conn->inBuffer + conn->inEnd,
-                                                conn->inBufSize - conn->inEnd);
-       else
-#endif
-               nread = recv(conn->sock, conn->inBuffer + conn->inEnd,
-                                        conn->inBufSize - conn->inEnd, 0);
+       nread = secure_read(conn, conn->inBuffer + conn->inEnd,
+                                               conn->inBufSize - conn->inEnd);
        if (nread < 0)
        {
                if (SOCK_ERRNO == EINTR)
@@ -563,14 +560,8 @@ retry3:
         * arrived.
         */
 retry4:
-#ifdef USE_SSL
-       if (conn->ssl)
-               nread = SSL_read(conn->ssl, conn->inBuffer + conn->inEnd,
-                                                conn->inBufSize - conn->inEnd);
-       else
-#endif
-               nread = recv(conn->sock, conn->inBuffer + conn->inEnd,
-                                        conn->inBufSize - conn->inEnd, 0);
+       nread = secure_read(conn, conn->inBuffer + conn->inEnd,
+                                               conn->inBufSize - conn->inEnd);
        if (nread < 0)
        {
                if (SOCK_ERRNO == EINTR)
@@ -611,6 +602,7 @@ definitelyFailed:
                           "\tThis probably means the server terminated abnormally\n"
                                                 "\tbefore or while processing the request.\n"));
        conn->status = CONNECTION_BAD;          /* No more connection to backend */
+       secure_close(conn);
 #ifdef WIN32
        closesocket(conn->sock);
 #else
@@ -650,23 +642,9 @@ pqSendSome(PGconn *conn)
        /* while there's still data to send */
        while (len > 0)
        {
-               /* Prevent being SIGPIPEd if backend has closed the connection. */
-#ifndef WIN32
-               pqsigfunc       oldsighandler = pqsignal(SIGPIPE, SIG_IGN);
-#endif
-
                int                     sent;
 
-#ifdef USE_SSL
-               if (conn->ssl)
-                       sent = SSL_write(conn->ssl, ptr, len);
-               else
-#endif
-                       sent = send(conn->sock, ptr, len, 0);
-
-#ifndef WIN32
-               pqsignal(SIGPIPE, oldsighandler);
-#endif
+               sent = secure_write(conn, ptr, len);
 
                if (sent < 0)
                {
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
new file mode 100644 (file)
index 0000000..d2d6d8c
--- /dev/null
@@ -0,0 +1,580 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-connect.c
+ *       functions related to setting up a secure connection to the backend.
+ *       Secure connections are expected to provide confidentiality,
+ *       message integrity and endpoint authentication.
+ *
+ *
+ * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *       $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-secure.c,v 1.1 2002/06/14 04:23:17 momjian Exp $
+ *       
+ * NOTES
+ *       The client *requires* a valid server certificate.  Since
+ *       SSH tunnels provide anonymous confidentiality, the presumption
+ *       is that sites that want endpoint authentication will use the
+ *       direct SSL support, while sites that are comfortable with
+ *       anonymous connections will use SSH tunnels.
+ *
+ *       This code verifies the server certificate, to detect simple
+ *       "man-in-the-middle" and "impersonation" attacks.  The 
+ *       server certificate, or better yet the CA certificate used
+ *       to sign the server certificate, should be present in the
+ *       "$HOME/.postgresql/root.crt" file.  If this file isn't
+ *       readable, or the server certificate can't be validated, 
+ *       secure_open_client() will return an error code.
+ *
+ *       Additionally, the server certificate's "common name" must
+ *       resolve to the other end of the socket.  This makes it
+ *       substantially harder to pull off a "man-in-the-middle" or
+ *       "impersonation" attack even if the server's private key
+ *       has been stolen.  This check limits acceptable network
+ *       layers to Unix sockets (weird, but legal), TCPv4 and TCPv6.
+ *
+ *       Unfortunately neither the current front- or back-end handle
+ *       failure gracefully, resulting in the backend hiccupping.
+ *       This points out problems in each (the frontend shouldn't even
+ *       try to do SSL if secure_initialize() fails, and the backend
+ *       shouldn't crash/recover if an SSH negotiation fails.  The 
+ *       backend definitely needs to be fixed, to prevent a "denial
+ *       of service" attack, but I don't know enough about how the 
+ *       backend works (especially that pre-SSL negotiation) to identify
+ *       a fix.
+ *
+ * OS DEPENDENCIES
+ *       The code currently assumes a POSIX password entry.  How should
+ *       Windows and Mac users be handled?
+ *
+ * PATCH LEVEL
+ *       milestone 1: fix basic coding errors
+ *       [*] existing SSL code pulled out of existing files.
+ *       [*] SSL_get_error() after SSL_read() and SSL_write(),
+ *           SSL_shutdown(), default to TLSv1.
+ *     
+ *       milestone 2: provide endpoint authentication (server)
+ *       [*] client verifies server cert
+ *       [*] client verifies server hostname
+ *
+ *       milestone 3: improve confidentially, support perfect forward secrecy
+ *       [ ] use 'random' file, read from '/dev/urandom?'
+ *       [ ] emphermal DH keys, default values
+ *
+ *       milestone 4: provide endpoint authentication (client)
+ *       [ ] server verifies client certificates
+ *
+ *       milestone 5: provide informational callbacks
+ *       [ ] provide informational callbacks
+ *
+ *       other changes
+ *       [ ] tcp-wrappers
+ *       [ ] more informative psql
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <sys/types.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+#include <string.h>
+
+#include "libpq-fe.h"
+#include "libpq-int.h"
+#include "fe-auth.h"
+#include "pqsignal.h"
+
+#ifdef WIN32
+#include "win32.h"
+#else
+#include <sys/socket.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#ifdef HAVE_NETINET_TCP_H
+#include <netinet/tcp.h>
+#endif
+#include <arpa/inet.h>
+#endif
+
+#ifndef HAVE_STRDUP
+#include "strdup.h"
+#endif
+
+#include <pwd.h>
+#include <sys/stat.h>
+
+#ifdef USE_SSL
+#include <openssl/ssl.h>
+#include <openssl/e_os.h>
+#endif /* USE_SSL */
+
+int secure_initialize(PGconn *);
+void secure_destroy(void);
+int secure_open_client(PGconn *);
+void secure_close(PGconn *);
+ssize_t secure_read(PGconn *, void *ptr, size_t len);
+ssize_t secure_write(PGconn *, const void *ptr, size_t len);
+
+#ifdef USE_SSL
+static int verify_cb(int ok, X509_STORE_CTX *ctx);
+static int verify_peer(PGconn *);
+static int initialize_SSL(PGconn *);
+static void destroy_SSL(void);
+static int open_client_SSL(PGconn *);
+static void close_SSL(PGconn *);
+static const char *SSLerrmessage(void);
+#endif
+
+#ifdef USE_SSL
+static SSL_CTX *SSL_context = NULL;
+#endif
+
+/* ------------------------------------------------------------ */
+/*           Procedures common to all secure sessions           */
+/* ------------------------------------------------------------ */
+
+/*
+ *     Initialize global context
+ */
+int
+secure_initialize (PGconn *conn)
+{
+       int r = 0;
+
+#ifdef USE_SSL
+       r = initialize_SSL(conn);
+#endif
+
+       return r;
+}
+
+/*
+ *     Destroy global context
+ */
+void
+secure_destroy (void)
+{
+#ifdef USE_SSL
+       destroy_SSL();
+#endif
+}
+
+/*
+ *     Attempt to negotiate secure session.
+ */
+int 
+secure_open_client (PGconn *conn)
+{
+       int r = 0;
+
+#ifdef USE_SSL
+       r = open_client_SSL(conn);
+#endif
+
+       return r;
+}
+
+/*
+ *     Close secure session.
+ */
+void
+secure_close (PGconn *conn)
+{
+#ifdef USE_SSL
+       if (conn->ssl)
+               close_SSL(conn);
+#endif
+}
+
+/*
+ *     Read data from a secure connection.
+ */
+ssize_t
+secure_read (PGconn *conn, void *ptr, size_t len)
+{
+       ssize_t n;
+
+#ifdef USE_SSL
+       if (conn->ssl)
+       {
+               n = SSL_read(conn->ssl, ptr, len);
+               switch (SSL_get_error(conn->ssl, n))
+               {
+               case SSL_ERROR_NONE:
+                       break;
+               case SSL_ERROR_WANT_READ:
+                       break;
+               case SSL_ERROR_SYSCALL:
+                       SOCK_ERRNO = get_last_socket_error();
+                       printfPQExpBuffer(&conn->errorMessage,
+                               libpq_gettext("SSL SYSCALL error: %s\n"), 
+                               SOCK_STRERROR(SOCK_ERRNO));
+                       break;
+               case SSL_ERROR_SSL:
+                       printfPQExpBuffer(&conn->errorMessage,
+                               libpq_gettext("SSL error: %s\n"), SSLerrmessage());
+                       /* fall through */
+               case SSL_ERROR_ZERO_RETURN:
+                       secure_close(conn);
+                       SOCK_ERRNO = ECONNRESET;
+                       n = -1;
+                       break;
+               }
+       }
+       else
+#endif
+       n = recv(conn->sock, ptr, len, 0);
+
+       return n;
+}
+
+/*
+ *     Write data to a secure connection.
+ */
+ssize_t
+secure_write (PGconn *conn, const void *ptr, size_t len)
+{
+       ssize_t n;
+
+#ifndef WIN32
+       pqsigfunc oldsighandler = pqsignal(SIGPIPE, SIG_IGN);
+#endif
+
+#ifdef USE_SSL
+       if (conn->ssl)
+       {
+               n = SSL_write(conn->ssl, ptr, len);
+               switch (SSL_get_error(conn->ssl, n))
+               {
+               case SSL_ERROR_NONE:
+                       break;
+               case SSL_ERROR_WANT_WRITE:
+                       break;
+               case SSL_ERROR_SYSCALL:
+                       SOCK_ERRNO = get_last_socket_error();
+                       printfPQExpBuffer(&conn->errorMessage,
+                               libpq_gettext("SSL SYSCALL error: %s\n"),
+                               SOCK_STRERROR(SOCK_ERRNO));
+                       break;
+               case SSL_ERROR_SSL:
+                       printfPQExpBuffer(&conn->errorMessage,
+                               libpq_gettext("SSL error: %s\n"), SSLerrmessage());
+                       /* fall through */
+               case SSL_ERROR_ZERO_RETURN:
+                       secure_close(conn);
+                       SOCK_ERRNO = ECONNRESET;
+                       n = -1;
+                       break;
+               }
+       }
+       else
+#endif
+       n = send(conn->sock, ptr, len, 0);
+
+#ifndef WIN32
+       pqsignal(SIGPIPE, oldsighandler);
+#endif
+
+       return n;
+}
+
+/* ------------------------------------------------------------ */
+/*                        SSL specific code                     */
+/* ------------------------------------------------------------ */
+#ifdef USE_SSL
+/*
+ *     Certificate verification callback
+ *
+ *     This callback allows us to log intermediate problems during
+ *     verification, but there doesn't seem to be a clean way to get
+ *     our PGconn * structure.  So we can't log anything!
+ *
+ *     This callback also allows us to override the default acceptance
+ *     criteria (e.g., accepting self-signed or expired certs), but
+ *     for now we accept the default checks.
+ */
+static int
+verify_cb (int ok, X509_STORE_CTX *ctx)
+{
+       return ok;
+}
+
+/*
+ *     Verify that common name resolves to peer.
+ *     This function is not thread-safe due to gethostbyname2().
+ */
+static int
+verify_peer (PGconn *conn)
+{
+       struct hostent *h = NULL;
+       struct sockaddr addr;
+       struct sockaddr_in *sin;
+       struct sockaddr_in6 *sin6;
+       socklen_t len;
+       char **s;
+       unsigned long l;
+
+       /* get the address on the other side of the socket */
+       len = sizeof(addr);
+       if (getpeername(conn->sock, &addr, &len) == -1)
+       {
+               printfPQExpBuffer(&conn->errorMessage,
+                       libpq_gettext("error querying socket: %s\n"), 
+                       SOCK_STRERROR(SOCK_ERRNO));
+               return -1;
+       }
+
+       /* weird, but legal case */
+       if (addr.sa_family == AF_UNIX)
+               return 0;
+
+       /* what do we know about the peer's common name? */
+       if ((h = gethostbyname2(conn->peer_cn, addr.sa_family)) == NULL)
+       {
+               printfPQExpBuffer(&conn->errorMessage,
+                       libpq_gettext("error getting information about host (%s): %s\n"),
+                       conn->peer_cn, hstrerror(h_errno));
+               return -1;
+       }
+
+       /* does the address match? */
+       switch (addr.sa_family)
+       {
+       case AF_INET:
+               sin = (struct sockaddr_in *) &addr;
+               for (s = h->h_addr_list; *s != NULL; s++)
+               {
+                       if (!memcmp(&sin->sin_addr.s_addr, *s, h->h_length))
+                               return 0;
+               }
+               break;
+
+       case AF_INET6:
+               sin6 = (struct sockaddr_in6 *) &addr;
+               for (s = h->h_addr_list; *s != NULL; s++)
+               {
+                       if (!memcmp(sin6->sin6_addr.in6_u.u6_addr8, *s, h->h_length))
+                               return 0;
+               }
+               break;
+
+       default:
+               printfPQExpBuffer(&conn->errorMessage,
+                       libpq_gettext("sorry, this protocol not yet supported\n"));
+               return -1;
+       }
+
+       /* the prior test should be definitive, but in practice
+        * it sometimes fails.  So we also check the aliases.  */
+       for (s = h->h_aliases; *s != NULL; s++)
+       {
+               if (strcasecmp(conn->peer_cn, *s) == 0)
+                       return 0;
+       }
+
+       /* generate protocol-aware error message */
+       switch (addr.sa_family)
+       {
+       case AF_INET:
+               sin = (struct sockaddr_in *) &addr;
+               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);
+               break;
+       default:
+               printfPQExpBuffer(&conn->errorMessage,
+                       libpq_gettext(
+                               "server common name '%s' does not resolve to peer address\n"),
+                       conn->peer_cn);
+       }
+
+       return -1;
+}
+
+/*
+ *     Initialize global SSL context.
+ */
+static int
+initialize_SSL (PGconn *conn)
+{
+       struct stat buf;
+       struct passwd *pwd;
+       char fnbuf[2048];
+
+       if (!SSL_context)
+       {
+               SSL_library_init();
+               SSL_load_error_strings();
+               SSL_context = SSL_CTX_new(TLSv1_method());
+               if (!SSL_context)
+               {
+                       printfPQExpBuffer(&conn->errorMessage,
+                               libpq_gettext("could not create SSL context: %s\n"),
+                                                         SSLerrmessage());
+                       return -1;
+               }
+       }
+
+       if ((pwd = getpwuid(getuid())) != NULL)
+       {
+               snprintf(fnbuf, sizeof fnbuf, "%s/.postgresql/root.crt",
+                       pwd->pw_dir);
+               if (stat(fnbuf, &buf) == -1)
+               {
+                       printfPQExpBuffer(&conn->errorMessage,
+                               libpq_gettext("could not read  root cert list(%s): %s"),
+                               fnbuf, strerror(errno));
+                       return -1;
+               }
+               if (!SSL_CTX_load_verify_locations(SSL_context, fnbuf, 0))
+               {
+                       printfPQExpBuffer(&conn->errorMessage,
+                               libpq_gettext("could not read root cert list (%s): %s"),
+                               fnbuf, SSLerrmessage());
+                       return -1;
+               }
+       }
+
+       SSL_CTX_set_verify(SSL_context, 
+               SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb);
+       SSL_CTX_set_verify_depth(SSL_context, 1);
+
+       return 0;
+}
+
+/*
+ *     Destroy global SSL context.
+ */
+static void
+destroy_SSL (void)
+{
+       if (SSL_context)
+       {
+               SSL_CTX_free(SSL_context);
+               SSL_context = NULL;
+       }
+}
+
+/*
+ *     Attempt to negotiate SSL connection.
+ */
+static int
+open_client_SSL (PGconn *conn)
+{
+       int r;
+
+       if (!(conn->ssl = SSL_new(SSL_context)) ||
+               !SSL_set_fd(conn->ssl, conn->sock) ||
+               SSL_connect(conn->ssl) <= 0)
+       {
+               printfPQExpBuffer(&conn->errorMessage,
+                       libpq_gettext("could not establish SSL connection: %s\n"),
+                                                 SSLerrmessage());
+               close_SSL(conn);
+               return -1;
+       }
+
+       /* check the certificate chain of the server */
+       /* this eliminates simple man-in-the-middle attacks and
+        * simple impersonations */
+       r = SSL_get_verify_result(conn->ssl);
+       if (r != X509_V_OK)
+       {
+               printfPQExpBuffer(&conn->errorMessage,
+                       libpq_gettext("certificate could not be validated: %s\n"),
+                       X509_verify_cert_error_string(r));
+               close_SSL(conn);
+               return -1;
+       }
+
+       /* pull out server distinguished and common names */
+       conn->peer = SSL_get_peer_certificate(conn->ssl);
+       if (conn->peer == NULL)
+       {
+               printfPQExpBuffer(&conn->errorMessage,
+                       libpq_gettext("certificate could not be obtained: %s\n"),
+                       SSLerrmessage());
+               close_SSL(conn);
+               return -1;
+       }
+
+       X509_NAME_oneline(X509_get_subject_name(conn->peer),
+               conn->peer_dn, sizeof(conn->peer_dn));
+       conn->peer_dn[sizeof(conn->peer_dn)-1] = '\0';
+
+       X509_NAME_get_text_by_NID(X509_get_subject_name(conn->peer),
+               NID_commonName, conn->peer_cn, SM_USER);
+       conn->peer_cn[SM_USER] = '\0';
+
+       /* verify that the common name resolves to peer */
+       /* this is necessary to eliminate man-in-the-middle attacks
+        * and impersonations where the attacker somehow learned
+        * the server's private key */
+       if (verify_peer(conn) == -1)
+       {
+               close_SSL(conn);
+               return -1;
+       }
+
+       return 0;
+}
+
+/*
+ *     Close SSL connection.
+ */
+static void
+close_SSL (PGconn *conn)
+{
+       if (conn->ssl)
+       {
+               SSL_shutdown(conn->ssl);
+               SSL_free(conn->ssl);
+               conn->ssl = NULL;
+       }
+}
+
+/*
+ * Obtain reason string for last SSL error
+ *
+ * Some caution is needed here since ERR_reason_error_string will
+ * return NULL if it doesn't recognize the error code.  We don't
+ * want to return NULL ever.
+ */
+static const char *
+SSLerrmessage(void)
+{
+       unsigned long   errcode;
+       const char         *errreason;
+       static char             errbuf[32];
+
+       errcode = ERR_get_error();
+       if (errcode == 0)
+               return "No SSL error reported";
+       errreason = ERR_reason_error_string(errcode);
+       if (errreason != NULL)
+               return errreason;
+       snprintf(errbuf, sizeof(errbuf), "SSL error code %lu", errcode);
+       return errbuf;
+}
+
+/*
+ *     Return pointer to SSL object.
+ */
+SSL *
+PQgetssl(PGconn *conn)
+{
+       if (!conn)
+               return NULL;
+       return conn->ssl;
+}
+#endif /* USE_SSL */
index ec8deb0083044cc8492012602ed315e0fc49a367..90a0186d276d393b145b70c92ac7cde4852b087c 100644 (file)
@@ -12,7 +12,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: libpq-int.h,v 1.48 2002/06/14 04:09:37 momjian Exp $
+ * $Id: libpq-int.h,v 1.49 2002/06/14 04:23:17 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -270,6 +270,9 @@ struct pg_conn
        bool            allow_ssl_try;  /* Allowed to try SSL negotiation */
        bool            require_ssl;    /* Require SSL to make connection */
        SSL                *ssl;                        /* SSL status, if have SSL connection */
+       X509       *peer;                       /* X509 cert of server */
+       char            peer_dn[256+1]; /* peer distinguished name */
+       char            peer_cn[SM_USER+1]; /* peer common name */
 #endif
 
        /* Buffer for current error message */