]> granicus.if.org Git - postgresql/commitdiff
SSL patch that adds support for optional client certificates.
authorBruce Momjian <bruce@momjian.us>
Fri, 14 Jun 2002 04:36:58 +0000 (04:36 +0000)
committerBruce Momjian <bruce@momjian.us>
Fri, 14 Jun 2002 04:36:58 +0000 (04:36 +0000)
If the user has certificates in $HOME/.postgresql/postgresql.crt
and $HOME/.postgresql/postgresql.key exist, they are provided
to the server.  The certificate used to sign this cert must be
known to the server, in $DataDir/root.crt.  If successful, the
cert's "common name" is logged.

Client certs are not used for authentication, but they could be
via the port->peer (X509 *), port->peer_dn (char *) or
port->peer_cn (char *) fields.  Or any other function could be
used, e.g., many sites like the issuer + serial number hash.

Bear Giles

src/backend/libpq/be-secure.c
src/include/libpq/libpq-be.h
src/interfaces/libpq/fe-secure.c

index 4b2b3e852085740ce134e5487c8c39cc128c3898..fab5e99aa9a4444ad8b08025b2d3c820033f9e30 100644 (file)
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/libpq/be-secure.c,v 1.4 2002/06/14 04:35:02 momjian Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/libpq/be-secure.c,v 1.5 2002/06/14 04:36:58 momjian Exp $
  *
  *       Since the server static private key ($DataDir/server.key)
  *       will normally be stored unencrypted so that the database
@@ -62,7 +62,7 @@
  *       [*] private key permissions
  *
  *       milestone 4: provide endpoint authentication (client)
- *       [ ] server verifies client certificates
+ *       [*] server verifies client certificates
  *
  *       milestone 5: provide informational callbacks
  *       [ ] provide informational callbacks
@@ -124,6 +124,7 @@ ssize_t secure_write(Port *, const void *ptr, size_t len);
 static DH *load_dh_file(int keylength);
 static DH *load_dh_buffer(const char *, size_t);
 static DH *tmp_dh_cb(SSL *s, int is_export, int keylength);
+static int verify_cb(int, X509_STORE_CTX *);
 static int initialize_SSL(void);
 static void destroy_SSL(void);
 static int open_server_SSL(Port *);
@@ -137,7 +138,7 @@ static const char *SSLerrmessage(void);
  *     (total in both directions) before we require renegotiation.
  */
 #define RENEGOTIATION_LIMIT    (64 * 1024)
-
+#define CA_PATH        NULL
 static SSL_CTX *SSL_context = NULL;
 #endif
 
@@ -521,6 +522,24 @@ tmp_dh_cb (SSL *s, int is_export, int keylength)
        return r;
 }
 
+/*
+ *     Certificate verification callback
+ *
+ *     This callback allows us to log intermediate problems during
+ *     verification, but for now we'll see if the final error message
+ *     contains enough information.
+ *
+ *     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;
+}
+
+
 /*
  *     Initialize global SSL context.
  */
@@ -583,6 +602,17 @@ initialize_SSL (void)
        SSL_CTX_set_tmp_dh_callback(SSL_context, tmp_dh_cb);
        SSL_CTX_set_options(SSL_context, SSL_OP_SINGLE_DH_USE);
 
+       /* accept client certificates, but don't require them. */
+       snprintf(fnbuf, sizeof fnbuf, "%s/root.crt", DataDir);
+       if (!SSL_CTX_load_verify_locations(SSL_context, fnbuf, CA_PATH))
+       {
+               postmaster_error("could not read root cert file (%s): %s",
+                                                fnbuf, SSLerrmessage());
+               ExitPostmaster(1);
+       }
+       SSL_CTX_set_verify(SSL_context, 
+               SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, verify_cb);
+
        return 0;
 }
 
@@ -615,6 +645,24 @@ open_server_SSL (Port *port)
        }
        port->count = 0;
 
+       /* get client certificate, if available. */
+       port->peer = SSL_get_peer_certificate(port->ssl);
+       if (port->peer == NULL)
+       {
+               strncpy(port->peer_dn, "(anonymous)", sizeof (port->peer_dn));
+               strncpy(port->peer_cn, "(anonymous)", sizeof (port->peer_cn));
+       }
+       else
+       {
+               X509_NAME_oneline(X509_get_subject_name(port->peer),
+                       port->peer_dn, sizeof (port->peer_dn));
+               port->peer_dn[sizeof(port->peer_dn)-1] = '\0';
+               X509_NAME_get_text_by_NID(X509_get_subject_name(port->peer),
+                       NID_commonName, port->peer_cn, sizeof (port->peer_cn));
+               port->peer_cn[sizeof(port->peer_cn)-1] = '\0';
+       }
+       elog(DEBUG, "secure connection from '%s'", port->peer_cn);
+
        return 0;
 }
 
index 76a4d1af624b156ad2e1c267f85b7dee573d3720..18236db6d4cace165ee98b168395a515507c2ed3 100644 (file)
@@ -11,7 +11,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: libpq-be.h,v 1.30 2002/06/14 04:33:53 momjian Exp $
+ * $Id: libpq-be.h,v 1.31 2002/06/14 04:36:58 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -70,6 +70,9 @@ typedef struct Port
         */
 #ifdef USE_SSL
        SSL                *ssl;
+       X509       *peer;
+       char            peer_dn[128 + 1];
+       char            peer_cn[SM_USER + 1];
        unsigned long count;
 #endif
 } Port;
index 632c451366e5ed96740e72bda3a7def8cc90d28a..3240be892e0a9e2696cabee923fbaa315c25eb1e 100644 (file)
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-secure.c,v 1.2 2002/06/14 04:31:49 momjian Exp $
+ *       $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-secure.c,v 1.3 2002/06/14 04:36:58 momjian Exp $
  *       
  * NOTES
  *       The client *requires* a valid server certificate.  Since
  *       should normally be stored encrypted.  However we still
  *       support EPH since it's useful for other reasons.
  *
+ *       ...
+ *
+ *       Client certificates are supported, if the server requests
+ *       or requires them.  Client certificates can be used for
+ *       authentication, to prevent sessions from being hijacked,
+ *       or to allow "road warriors" to access the database while
+ *       keeping it closed to everyone else.
+ *
+ *       The user's certificate and private key are located in
+ *         $HOME/.postgresql/postgresql.crt
+ *       and
+ *         $HOME/.postgresql/postgresql.key
+ *       respectively.
+ *
  * OS DEPENDENCIES
  *       The code currently assumes a POSIX password entry.  How should
  *       Windows and Mac users be handled?
@@ -71,7 +85,7 @@
  *       [*] emphermal DH keys, default values
  *
  *       milestone 4: provide endpoint authentication (client)
- *       [ ] server verifies client certificates
+ *       [*] server verifies client certificates
  *
  *       milestone 5: provide informational callbacks
  *       [ ] provide informational callbacks
@@ -135,6 +149,7 @@ static int verify_peer(PGconn *);
 static DH *load_dh_file(int keylength);
 static DH *load_dh_buffer(const char *, size_t);
 static DH *tmp_dh_cb(SSL *s, int is_export, int keylength);
+static int client_cert_cb(SSL *, X509 **, EVP_PKEY **);
 static int initialize_SSL(PGconn *);
 static void destroy_SSL(void);
 static int open_client_SSL(PGconn *);
@@ -614,6 +629,101 @@ tmp_dh_cb (SSL *s, int is_export, int keylength)
        return r;
 }
 
+/*
+ *     Callback used by SSL to load client cert and key.
+ *     This callback is only called when the server wants a
+ *     client cert.
+ *
+ *     Returns 1 on success, 0 on no data, -1 on error.
+ */
+static int
+client_cert_cb (SSL *ssl, X509 **x509, EVP_PKEY **pkey)
+{
+       struct passwd *pwd;
+       struct stat buf, buf2;
+       char fnbuf[2048];
+       FILE *fp;
+       PGconn *conn = (PGconn *) SSL_get_app_data(ssl);
+       int (*cb)() = NULL; /* how to read user password */
+
+       if ((pwd = getpwuid(getuid())) == NULL)
+       {
+               printfPQExpBuffer(&conn->errorMessage, 
+                       libpq_gettext("unable to get user information\n"));
+               return -1;
+       }
+
+       /* read the user certificate */
+       snprintf(fnbuf, sizeof fnbuf, "%s/.postgresql/postgresql.crt",
+               pwd->pw_dir);
+       if (stat(fnbuf, &buf) == -1)
+               return 0;
+       if ((fp = fopen(fnbuf, "r")) == NULL)
+       {
+               printfPQExpBuffer(&conn->errorMessage, 
+                       libpq_gettext("unable to open certificate (%s): %s\n"),
+                       fnbuf, strerror(errno));
+               return -1;
+       }
+       if (PEM_read_X509(fp, x509, NULL, NULL) == NULL)
+       {
+               printfPQExpBuffer(&conn->errorMessage, 
+                       libpq_gettext("unable to read certificate (%s): %s\n"),
+                       fnbuf, SSLerrmessage());
+               fclose(fp);
+               return -1;
+       }
+       fclose(fp);
+
+       /* read the user key */
+       snprintf(fnbuf, sizeof fnbuf, "%s/.postgresql/postgresql.key",
+               pwd->pw_dir);
+       if (stat(fnbuf, &buf) == -1)
+       {
+               printfPQExpBuffer(&conn->errorMessage, 
+                       libpq_gettext("certificate present, but not private key (%s)\n"),
+                       fnbuf);
+               X509_free(*x509);
+               return 0;
+       }
+       if (!S_ISREG(buf.st_mode) || (buf.st_mode & 0077) ||
+               buf.st_uid != getuid())
+       {
+               printfPQExpBuffer(&conn->errorMessage, 
+                       libpq_gettext("private key has bad permissions (%s)\n"), fnbuf);
+               X509_free(*x509);
+               return -1;
+       }
+       if ((fp = fopen(fnbuf, "r")) == NULL)
+       {
+               printfPQExpBuffer(&conn->errorMessage, 
+                       libpq_gettext("unable to open private key file (%s): %s\n"),
+                       fnbuf, strerror(errno));
+               X509_free(*x509);
+               return -1;
+       }
+       if (fstat(fileno(fp), &buf2) == -1 ||
+               buf.st_dev != buf2.st_dev || buf.st_ino != buf2.st_ino)
+       {
+               printfPQExpBuffer(&conn->errorMessage, 
+                       libpq_gettext("private key changed under us (%s)\n"), fnbuf);
+               X509_free(*x509);
+               return -1;
+       }
+       if (PEM_read_PrivateKey(fp, pkey, cb, NULL) == NULL)
+       {
+               printfPQExpBuffer(&conn->errorMessage, 
+                       libpq_gettext("unable to read private key (%s): %s\n"),
+                       fnbuf, SSLerrmessage());
+               X509_free(*x509);
+               fclose(fp);
+               return -1;
+       }
+       fclose(fp);
+
+       return 1;
+}
+
 /*
  *     Initialize global SSL context.
  */
@@ -666,6 +776,9 @@ initialize_SSL (PGconn *conn)
        SSL_CTX_set_tmp_dh_callback(SSL_context, tmp_dh_cb);
        SSL_CTX_set_options(SSL_context, SSL_OP_SINGLE_DH_USE);
 
+       /* set up mechanism to provide client certificate, if available */
+       SSL_CTX_set_client_cert_cb(SSL_context, client_cert_cb);
+
        return 0;
 }
 
@@ -691,6 +804,7 @@ open_client_SSL (PGconn *conn)
        int r;
 
        if (!(conn->ssl = SSL_new(SSL_context)) ||
+               !SSL_set_app_data(conn->ssl, conn) ||
                !SSL_set_fd(conn->ssl, conn->sock) ||
                SSL_connect(conn->ssl) <= 0)
        {