From c7b08050d9a2b68b27045b36ff4c9a3db85a55e4 Mon Sep 17 00:00:00 2001
From: Bruce Momjian <bruce@momjian.us>
Date: Fri, 16 Feb 2007 02:59:41 +0000
Subject: [PATCH] SSL improvements:

	o read global SSL configuration file
	o add GUC "ssl_ciphers" to control allowed ciphers
	o add libpq environment variable PGSSLKEY to control SSL hardware keys

Victor B. Wagner
---
 doc/src/sgml/config.sgml                      |  16 +-
 doc/src/sgml/libpq.sgml                       |  43 +++++-
 doc/src/sgml/runtime.sgml                     |  25 +++-
 src/backend/libpq/be-secure.c                 |  15 +-
 src/backend/postmaster/postmaster.c           |   3 +-
 src/backend/utils/misc/guc.c                  |  12 +-
 src/backend/utils/misc/postgresql.conf.sample |   1 +
 src/include/postmaster/postmaster.h           |   3 +-
 src/interfaces/libpq/fe-secure.c              | 138 ++++++++++++------
 9 files changed, 202 insertions(+), 54 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index e5c73b4e361..aa5c15f1a01 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/config.sgml,v 1.110 2007/02/08 15:46:03 momjian Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/config.sgml,v 1.111 2007/02/16 02:59:40 momjian Exp $ -->
 
 <chapter Id="runtime-config">
   <title>Server Configuration</title>
@@ -569,6 +569,20 @@ SET ENABLE_SEQSCAN TO OFF;
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-ssl-ciphers" xreflabel="ssl-ciphers">
+      <term><varname>ssl_ciphers> (<type>string</type>)</term>
+      <indexterm>
+       <primary><varname>ssl_ciphers</> configuration parameter</primary>
+      </indexterm>
+      <listitem>
+       <para>
+        Specifies a list of <acronym>SSL</> ciphers which can be used to
+        establish secure connections. See the <application>openssl</>
+        manual page for a list of supported ciphers.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="guc-password-encryption" xreflabel="password_encryption">
       <term><varname>password_encryption</varname> (<type>boolean</type>)</term>
       <indexterm>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 4c017334e1d..dd8ab08fe2b 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/libpq.sgml,v 1.228 2007/02/06 03:03:11 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/libpq.sgml,v 1.229 2007/02/16 02:59:40 momjian Exp $ -->
 
  <chapter id="libpq">
   <title><application>libpq</application> - C Library</title>
@@ -4174,6 +4174,18 @@ setting, and is only available if
 </listitem>
 <listitem>
 <para>
+<indexterm>
+<primary><envar>PGSSLKEY</envar></primary>
+</indexterm>
+<envar>PGSSLKEY</envar>
+specifies the hardware token which stores the secret key for the client
+certificate, instead of a file. The value of this variable should consist
+of a colon-separated engine name (engines are <productname>OpenSSL</>
+loadable modules) and an engine-specific key identifier.
+</para>
+</listitem>
+<listitem>
+<para>
 <indexterm>
  <primary><envar>PGKRBSRVNAME</envar></primary>
 </indexterm>
@@ -4438,19 +4450,44 @@ ldap://ldap.mycompany.com/dc=mycompany,dc=com?uniqueMember?one?(cn=mydatabase)
    for increased security. See <xref linkend="ssl-tcp"> for details
    about the server-side <acronym>SSL</> functionality.
   </para>
-
+  <para>
+  <application>libpq</application> reads the system-wide
+  <productname>OpenSSL</productname> configuration file. By default, this
+  file is named <filename>openssl.cnf</filename> and is located in the
+  directory reported by <application>openssl</>:
+  <programlisting>
+  openssl version -d
+  </programlisting>
+  The default can be overriden by setting environment variable
+  <envar>OPENSSL_CONF</envar> to the name of the desired configuration
+  file.
+  </para>
   <para>
    If the server demands a client certificate, 
    <application>libpq</application>
    will send the certificate stored in file
    <filename>~/.postgresql/postgresql.crt</> within the user's home directory.
    A matching private key file <filename>~/.postgresql/postgresql.key</>
-   must also be present, and must not be world-readable.
+   must also be present, and must not be world-readable, unless the secret
+   key is stored in a hardware token, as specified by
+   <envar>PGSSLKEY</envar>.
    (On Microsoft Windows these files are named
    <filename>%APPDATA%\postgresql\postgresql.crt</filename> and
    <filename>%APPDATA%\postgresql\postgresql.key</filename>.)
   </para>
 
+  <para>
+   If the environment variable <envar>PGSSLKEY</envar> is set, its value
+   should consist of a colon-separated engine name and key identifier. In
+   this case, <application>libpq</application> will load the specified
+   engine, i.e. the <productname>OpenSSL</> module which supports special
+   hardware and reference the key with the specified identifier.
+   Identifiers are engine-specific. Typically, cryptography hardware tokens
+   do not reveal secret keys to the application. Instead, applications
+   delegate all cryptography operations which require the secret key to
+   the hardware token.
+  </para>	
+	
   <para>
    If the file <filename>~/.postgresql/root.crt</> is present in the user's
    home directory,
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 4baec332766..7bed97a86f3 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/runtime.sgml,v 1.376 2007/02/01 00:28:18 momjian Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/runtime.sgml,v 1.377 2007/02/16 02:59:40 momjian Exp $ -->
 
 <chapter Id="runtime">
  <title>Operating System Environment</title>
@@ -1515,6 +1515,25 @@ $ <userinput>kill -INT `head -1 /usr/local/pgsql/data/postmaster.pid`</userinput
    require use of <acronym>SSL</> for some or all connections.
   </para>
 
+  <para>
+   <productname>OpenSSL</productname> supports a wide range of ciphers
+   and authentication algorithms, whose strength varies significantly.
+   You can restrict the list of ciphers which can be used to connect to
+   your server using the <xref linkend="guc-ssl-ciphers"> parameter.
+  </para>
+
+  <para>
+   <productname>PostgreSQL</productname> reads a system-wide
+   <productname>OpenSSL</productname> configuration file. By default this
+   file is named <filename>openssl.cnf</filename> and is located in the
+   directory reported by <application>openssl</>:
+   <programlisting>
+   openssl version -d
+   </programlisting>
+   This default can be overriden by setting environment variable
+   <envar>OPENSSL_CONF</envar> to the name of desired configuration file.
+  </para>
+
   <para>
    For details on how to create your server private key and certificate,
    refer to the <productname>OpenSSL</> documentation. A
@@ -1528,8 +1547,8 @@ $ <userinput>kill -INT `head -1 /usr/local/pgsql/data/postmaster.pid`</userinput
 <programlisting>
 openssl req -new -text -out server.req
 </programlisting>
-   Fill out the information that <command>openssl</> asks for. Make sure
-   that you enter the local host name as <quote>Common Name</>; the challenge
+   Fill out the information that <application>openssl</> asks for. Make sure
+   you enter the local host name as <quote>Common Name</>; the challenge
    password can be left blank. The program will generate a key that is
    passphrase protected; it will not accept a passphrase that is less
    than four characters long. To remove the passphrase (as you must if
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 8a54275d9cd..1fb648fb8ed 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/libpq/be-secure.c,v 1.77 2007/02/07 00:52:35 petere Exp $
+ *	  $PostgreSQL: pgsql/src/backend/libpq/be-secure.c,v 1.78 2007/02/16 02:59:40 momjian Exp $
  *
  *	  Since the server static private key ($DataDir/server.key)
  *	  will normally be stored unencrypted so that the database
@@ -92,6 +92,10 @@
 #ifdef USE_SSL
 #include <openssl/ssl.h>
 #include <openssl/dh.h>
+#if SSLEAY_VERSION_NUMBER >= 0x0907000L
+#include <openssl/conf.h>
+#endif
+
 #endif
 
 #include "libpq/libpq.h"
@@ -125,6 +129,10 @@ static const char *SSLerrmessage(void);
 #define RENEGOTIATION_LIMIT (512 * 1024 * 1024)
 
 static SSL_CTX *SSL_context = NULL;
+
+/* GUC variable controlling SSL cipher list*/
+extern char *SSLCipherSuites;
+
 #endif
 
 /* ------------------------------------------------------------ */
@@ -719,6 +727,9 @@ initialize_SSL(void)
 
 	if (!SSL_context)
 	{
+#if SSLEAY_VERSION_NUMBER >= 0x0907000L
+		OPENSSL_config(NULL);
+#endif
 		SSL_library_init();
 		SSL_load_error_strings();
 		SSL_context = SSL_CTX_new(SSLv23_method());
@@ -780,7 +791,7 @@ initialize_SSL(void)
 	SSL_CTX_set_options(SSL_context, SSL_OP_SINGLE_DH_USE | SSL_OP_NO_SSLv2);
 
 	/* setup the allowed cipher list */
-	if (SSL_CTX_set_cipher_list(SSL_context, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH") != 1)
+	if (SSL_CTX_set_cipher_list(SSL_context, SSLCipherSuites) != 1)
 		elog(FATAL, "could not set the cipher list (no valid ciphers available)");
 
 	/*
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index f7411210a74..0a0a677e6ef 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -37,7 +37,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/postmaster/postmaster.c,v 1.523 2007/02/16 02:10:07 alvherre Exp $
+ *	  $PostgreSQL: pgsql/src/backend/postmaster/postmaster.c,v 1.524 2007/02/16 02:59:41 momjian Exp $
  *
  * NOTES
  *
@@ -187,6 +187,7 @@ static int	SendStop = false;
 
 /* still more option variables */
 bool		EnableSSL = false;
+char	   *SSLCipherSuites;
 bool		SilentMode = false; /* silent mode (-S) */
 
 int			PreAuthDelay = 0;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index e66573d7380..b5d93d6d64d 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -10,7 +10,7 @@
  * Written by Peter Eisentraut <peter_e@gmx.net>.
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.374 2007/02/14 03:08:44 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.375 2007/02/16 02:59:41 momjian Exp $
  *
  *--------------------------------------------------------------------
  */
@@ -2314,6 +2314,16 @@ static struct config_string ConfigureNamesString[] =
 		NULL, assign_temp_tablespaces, NULL
 	},
 
+	{
+		{"ssl_ciphers", PGC_POSTMASTER, CONN_AUTH_SECURITY,
+			gettext_noop("Sets the list of allowed SSL ciphers."),
+			NULL,
+			GUC_SUPERUSER_ONLY
+		},
+		&SSLCipherSuites,
+		"ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH", NULL, NULL
+	},
+			
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 2e708b11623..ca5b2aafb11 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -74,6 +74,7 @@
 
 #authentication_timeout = 1min		# 1s-600s
 #ssl = off				# (change requires restart)
+#ssl_ciphers = 'ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH' # List of ciphers to use
 #password_encryption = on
 #db_user_namespace = off
 
diff --git a/src/include/postmaster/postmaster.h b/src/include/postmaster/postmaster.h
index 26818c17344..811e65af55b 100644
--- a/src/include/postmaster/postmaster.h
+++ b/src/include/postmaster/postmaster.h
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/postmaster/postmaster.h,v 1.15 2007/01/05 22:19:57 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/postmaster/postmaster.h,v 1.16 2007/02/16 02:59:41 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -15,6 +15,7 @@
 
 /* GUC options */
 extern bool EnableSSL;
+extern char *SSLCipherSuites;
 extern bool SilentMode;
 extern int	ReservedBackends;
 extern int	PostPortNumber;
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index 2d387b19d27..f97d4c53344 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/interfaces/libpq/fe-secure.c,v 1.92 2007/02/08 11:10:27 petere Exp $
+ *	  $PostgreSQL: pgsql/src/interfaces/libpq/fe-secure.c,v 1.93 2007/02/16 02:59:41 momjian Exp $
  *
  * NOTES
  *	  [ Most of these notes are wrong/obsolete, but perhaps not all ]
@@ -111,6 +111,12 @@
 
 #ifdef USE_SSL
 #include <openssl/ssl.h>
+#if (SSLEAY_VERSION_NUMBER >= 0x00907000L)
+#include <openssl/conf.h> 
+#endif
+#if (SSLEAY_VERSION_NUMBER >= 0x00907000L) && !defined(OPENSSL_NO_ENGINE)
+#include <openssl/engine.h>
+#endif
 #endif   /* USE_SSL */
 
 
@@ -606,54 +612,99 @@ client_cert_cb(SSL *ssl, X509 **x509, EVP_PKEY **pkey)
 	}
 	fclose(fp);
 
-	/* read the user key */
-	snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_KEY_FILE);
-	if (stat(fnbuf, &buf) == -1)
-	{
-		printfPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("certificate present, but not private key file \"%s\"\n"),
-						  fnbuf);
-		return 0;
-	}
-#ifndef WIN32
-	if (!S_ISREG(buf.st_mode) || (buf.st_mode & 0077) ||
-		buf.st_uid != geteuid())
-	{
-		printfPQExpBuffer(&conn->errorMessage,
-			libpq_gettext("private key file \"%s\" has wrong permissions\n"),
-						  fnbuf);
-		return 0;
-	}
-#endif
-	if ((fp = fopen(fnbuf, "r")) == NULL)
-	{
-		printfPQExpBuffer(&conn->errorMessage,
-			   libpq_gettext("could not open private key file \"%s\": %s\n"),
-						  fnbuf, pqStrerror(errno, sebuf, sizeof(sebuf)));
-		return 0;
-	}
-#ifndef WIN32
-	if (fstat(fileno(fp), &buf2) == -1 ||
-		buf.st_dev != buf2.st_dev || buf.st_ino != buf2.st_ino)
+#if (SSLEAY_VERSION_NUMBER >= 0x00907000L) && !defined(OPENSSL_NO_ENGINE)
+	if (getenv("PGSSLKEY"))
 	{
-		printfPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("private key file \"%s\" changed during execution\n"), fnbuf);
-		return 0;
+		/* read the user key from engine */
+		char	*engine_env = getenv("PGSSLKEY");
+		char	*engine_colon = strchr(engine_env, ':');
+		char	*engine_str;
+		ENGINE	*engine_ptr = NULL;
+
+		if (!engine_colon)
+		{
+			printfPQExpBuffer(&conn->errorMessage,
+				libpq_gettext("invalid value of PGSSLKEY environment variable\n"));
+			return 0;
+		}
+
+		engine_str = malloc(engine_colon - engine_env + 1);
+		strlcpy(engine_str, engine_env, engine_colon - engine_env + 1);
+		if ((engine_ptr = ENGINE_by_id(engine_str)) == NULL)
+		{
+			char	  *err = SSLerrmessage();
+
+			printfPQExpBuffer(&conn->errorMessage,
+				libpq_gettext("could not load SSL engine \"%s\":%s\n"), engine_str, err);
+			free(engine_str);
+			SSLerrfree(err);
+			return 0;
+		}	
+		if ((*pkey = ENGINE_load_private_key(engine_ptr,
+						engine_colon + 1, NULL, NULL)) == NULL)
+		{
+			char	  *err = SSLerrmessage();
+
+			printfPQExpBuffer(&conn->errorMessage,
+				libpq_gettext("could not read private SSL key %s from engine \"%s\": %s\n"),
+							engine_colon + 1, engine_str, err);
+			SSLerrfree(err);
+			free(engine_str);
+			return 0;
+		}		
+		free(engine_str);
 	}
+	else
 #endif
-	if (PEM_read_PrivateKey(fp, pkey, NULL, NULL) == NULL)
 	{
-		char	   *err = SSLerrmessage();
+		/* read the user key from file*/
+		snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_KEY_FILE);
+		if (stat(fnbuf, &buf) == -1)
+		{
+			printfPQExpBuffer(&conn->errorMessage,
+							libpq_gettext("certificate present, but not private key file \"%s\"\n"),
+							fnbuf);
+			return 0;
+		}
+	#ifndef WIN32
+		if (!S_ISREG(buf.st_mode) || (buf.st_mode & 0077) ||
+			buf.st_uid != geteuid())
+		{
+			printfPQExpBuffer(&conn->errorMessage,
+				libpq_gettext("private key file \"%s\" has wrong permissions\n"),
+							fnbuf);
+			return 0;
+		}
+	#endif
+		if ((fp = fopen(fnbuf, "r")) == NULL)
+		{
+			printfPQExpBuffer(&conn->errorMessage,
+				libpq_gettext("could not open private key file \"%s\": %s\n"),
+							fnbuf, pqStrerror(errno, sebuf, sizeof(sebuf)));
+			return 0;
+		}
+	#ifndef WIN32
+		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 file \"%s\" changed during execution\n"), fnbuf);
+			return 0;
+		}
+	#endif
+		if (PEM_read_PrivateKey(fp, pkey, NULL, NULL) == NULL)
+		{
+			char	   *err = SSLerrmessage();
 
-		printfPQExpBuffer(&conn->errorMessage,
-			   libpq_gettext("could not read private key file \"%s\": %s\n"),
-						  fnbuf, err);
-		SSLerrfree(err);
+			printfPQExpBuffer(&conn->errorMessage,
+				libpq_gettext("could not read private key file \"%s\": %s\n"),
+							fnbuf, err);
+			SSLerrfree(err);
+			fclose(fp);
+			return 0;
+		}
 		fclose(fp);
-		return 0;
 	}
-	fclose(fp);
-
 	/* verify that the cert and key go together */
 	if (!X509_check_private_key(*x509, *pkey))
 	{
@@ -737,6 +788,9 @@ init_ssl_system(PGconn *conn)
 	{
 		if (pq_initssllib)
 		{
+#if (SSLEAY_VERSION_NUMBER >= 0x00907000L) 
+			OPENSSL_config(NULL);
+#endif			
 			SSL_library_init();
 			SSL_load_error_strings();
 		}
-- 
GitLab