aboutsummaryrefslogtreecommitdiff
path: root/src/backend/libpq/be-secure-openssl.c
diff options
context:
space:
mode:
authorHeikki Linnakangas <heikki.linnakangas@iki.fi>2014-08-11 11:54:19 +0300
committerHeikki Linnakangas <heikki.linnakangas@iki.fi>2014-08-11 11:54:19 +0300
commit680513ab79c7e12e402a2aad7921b95a25a4bcc8 (patch)
treec2a5b1debb5599ae4a3522be921a78a6f1cf35c3 /src/backend/libpq/be-secure-openssl.c
parent6aa61580e08d58909b2a8845a4087b7699335ee0 (diff)
downloadpostgresql-680513ab79c7e12e402a2aad7921b95a25a4bcc8.tar.gz
postgresql-680513ab79c7e12e402a2aad7921b95a25a4bcc8.zip
Break out OpenSSL-specific code to separate files.
This refactoring is in preparation for adding support for other SSL implementations, with no user-visible effects. There are now two #defines, USE_OPENSSL which is defined when building with OpenSSL, and USE_SSL which is defined when building with any SSL implementation. Currently, OpenSSL is the only implementation so the two #defines go together, but USE_SSL is supposed to be used for implementation-independent code. The libpq SSL code is changed to use a custom BIO, which does all the raw I/O, like we've been doing in the backend for a long time. That makes it possible to use MSG_NOSIGNAL to block SIGPIPE when using SSL, which avoids a couple of syscall for each send(). Probably doesn't make much performance difference in practice - the SSL encryption is expensive enough to mask the effect - but it was a natural result of this refactoring. Based on a patch by Martijn van Oosterhout from 2006. Briefly reviewed by Alvaro Herrera, Andreas Karlsson, Jeff Janes.
Diffstat (limited to 'src/backend/libpq/be-secure-openssl.c')
-rw-r--r--src/backend/libpq/be-secure-openssl.c1045
1 files changed, 1045 insertions, 0 deletions
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
new file mode 100644
index 00000000000..e3a284b9ac5
--- /dev/null
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -0,0 +1,1045 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-openssl.c
+ * functions for OpenSSL support in the backend.
+ *
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-openssl.c
+ *
+ * Since the server static private key ($DataDir/server.key)
+ * will normally be stored unencrypted so that the database
+ * backend can restart automatically, it is important that
+ * we select an algorithm that continues to provide confidentiality
+ * even if the attacker has the server's private key. Ephemeral
+ * DH (EDH) keys provide this, and in fact provide Perfect Forward
+ * Secrecy (PFS) except for situations where the session can
+ * be hijacked during a periodic handshake/renegotiation.
+ * Even that backdoor can be closed if client certificates
+ * are used (since the imposter will be unable to successfully
+ * complete renegotiation).
+ *
+ * N.B., the static private key should still be protected to
+ * the largest extent possible, to minimize the risk of
+ * impersonations.
+ *
+ * Another benefit of EDH is that it allows the backend and
+ * clients to use DSA keys. DSA keys can only provide digital
+ * signatures, not encryption, and are often acceptable in
+ * jurisdictions where RSA keys are unacceptable.
+ *
+ * The downside to EDH is that it makes it impossible to
+ * use ssldump(1) if there's a problem establishing an SSL
+ * session. In this case you'll need to temporarily disable
+ * EDH by commenting out the callback.
+ *
+ * ...
+ *
+ * Because the risk of cryptanalysis increases as large
+ * amounts of data are sent with the same session key, the
+ * session keys are periodically renegotiated.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#ifdef HAVE_NETINET_TCP_H
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#endif
+
+#include <openssl/ssl.h>
+#include <openssl/dh.h>
+#if SSLEAY_VERSION_NUMBER >= 0x0907000L
+#include <openssl/conf.h>
+#endif
+#if (OPENSSL_VERSION_NUMBER >= 0x0090800fL) && !defined(OPENSSL_NO_ECDH)
+#include <openssl/ec.h>
+#endif
+
+#include "libpq/libpq.h"
+#include "tcop/tcopprot.h"
+#include "utils/memutils.h"
+
+
+
+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 void info_cb(const SSL *ssl, int type, int args);
+static const char *SSLerrmessage(void);
+
+/* are we in the middle of a renegotiation? */
+static bool in_ssl_renegotiation = false;
+
+static SSL_CTX *SSL_context = NULL;
+
+/* ------------------------------------------------------------ */
+/* Hardcoded values */
+/* ------------------------------------------------------------ */
+
+/*
+ * Hardcoded DH parameters, used in ephemeral DH keying.
+ * As discussed above, EDH protects the confidentiality of
+ * sessions even if the static private key is compromised,
+ * so we are *highly* motivated to ensure that we can use
+ * EDH even if the DBA... or an attacker... deletes the
+ * $DataDir/dh*.pem files.
+ *
+ * We could refuse SSL connections unless a good DH parameter
+ * file exists, but some clients may quietly renegotiate an
+ * unsecured connection without fully informing the user.
+ * Very uncool.
+ *
+ * Alternatively, the backend could attempt to load these files
+ * on startup if SSL is enabled - and refuse to start if any
+ * do not exist - but this would tend to piss off DBAs.
+ *
+ * If you want to create your own hardcoded DH parameters
+ * for fun and profit, review "Assigned Number for SKIP
+ * Protocols" (http://www.skip-vpn.org/spec/numbers.html)
+ * for suggestions.
+ */
+
+static const char file_dh512[] =
+"-----BEGIN DH PARAMETERS-----\n\
+MEYCQQD1Kv884bEpQBgRjXyEpwpy1obEAxnIByl6ypUM2Zafq9AKUJsCRtMIPWak\n\
+XUGfnHy9iUsiGSa6q6Jew1XpKgVfAgEC\n\
+-----END DH PARAMETERS-----\n";
+
+static const char file_dh1024[] =
+"-----BEGIN DH PARAMETERS-----\n\
+MIGHAoGBAPSI/VhOSdvNILSd5JEHNmszbDgNRR0PfIizHHxbLY7288kjwEPwpVsY\n\
+jY67VYy4XTjTNP18F1dDox0YbN4zISy1Kv884bEpQBgRjXyEpwpy1obEAxnIByl6\n\
+ypUM2Zafq9AKUJsCRtMIPWakXUGfnHy9iUsiGSa6q6Jew1XpL3jHAgEC\n\
+-----END DH PARAMETERS-----\n";
+
+static const char file_dh2048[] =
+"-----BEGIN DH PARAMETERS-----\n\
+MIIBCAKCAQEA9kJXtwh/CBdyorrWqULzBej5UxE5T7bxbrlLOCDaAadWoxTpj0BV\n\
+89AHxstDqZSt90xkhkn4DIO9ZekX1KHTUPj1WV/cdlJPPT2N286Z4VeSWc39uK50\n\
+T8X8dryDxUcwYc58yWb/Ffm7/ZFexwGq01uejaClcjrUGvC/RgBYK+X0iP1YTknb\n\
+zSC0neSRBzZrM2w4DUUdD3yIsxx8Wy2O9vPJI8BD8KVbGI2Ou1WMuF040zT9fBdX\n\
+Q6MdGGzeMyEstSr/POGxKUAYEY18hKcKctaGxAMZyAcpesqVDNmWn6vQClCbAkbT\n\
+CD1mpF1Bn5x8vYlLIhkmuquiXsNV6TILOwIBAg==\n\
+-----END DH PARAMETERS-----\n";
+
+static const char file_dh4096[] =
+"-----BEGIN DH PARAMETERS-----\n\
+MIICCAKCAgEA+hRyUsFN4VpJ1O8JLcCo/VWr19k3BCgJ4uk+d+KhehjdRqNDNyOQ\n\
+l/MOyQNQfWXPeGKmOmIig6Ev/nm6Nf9Z2B1h3R4hExf+zTiHnvVPeRBhjdQi81rt\n\
+Xeoh6TNrSBIKIHfUJWBh3va0TxxjQIs6IZOLeVNRLMqzeylWqMf49HsIXqbcokUS\n\
+Vt1BkvLdW48j8PPv5DsKRN3tloTxqDJGo9tKvj1Fuk74A+Xda1kNhB7KFlqMyN98\n\
+VETEJ6c7KpfOo30mnK30wqw3S8OtaIR/maYX72tGOno2ehFDkq3pnPtEbD2CScxc\n\
+alJC+EL7RPk5c/tgeTvCngvc1KZn92Y//EI7G9tPZtylj2b56sHtMftIoYJ9+ODM\n\
+sccD5Piz/rejE3Ome8EOOceUSCYAhXn8b3qvxVI1ddd1pED6FHRhFvLrZxFvBEM9\n\
+ERRMp5QqOaHJkM+Dxv8Cj6MqrCbfC4u+ZErxodzuusgDgvZiLF22uxMZbobFWyte\n\
+OvOzKGtwcTqO/1wV5gKkzu1ZVswVUQd5Gg8lJicwqRWyyNRczDDoG9jVDxmogKTH\n\
+AaqLulO7R8Ifa1SwF2DteSGVtgWEN8gDpN3RBmmPTDngyF2DHb5qmpnznwtFKdTL\n\
+KWbuHn491xNO25CQWMtem80uKw+pTnisBRF/454n1Jnhub144YRBoN8CAQI=\n\
+-----END DH PARAMETERS-----\n";
+
+/*
+ * Write data to a secure connection.
+ */
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len)
+{
+ ssize_t n;
+ int err;
+
+ /*
+ * If SSL renegotiations are enabled and we're getting close to the
+ * limit, start one now; but avoid it if there's one already in
+ * progress. Request the renegotiation 1kB before the limit has
+ * actually expired.
+ */
+ if (ssl_renegotiation_limit && !in_ssl_renegotiation &&
+ port->count > (ssl_renegotiation_limit - 1) * 1024L)
+ {
+ in_ssl_renegotiation = true;
+
+ /*
+ * The way we determine that a renegotiation has completed is by
+ * observing OpenSSL's internal renegotiation counter. Make sure
+ * we start out at zero, and assume that the renegotiation is
+ * complete when the counter advances.
+ *
+ * OpenSSL provides SSL_renegotiation_pending(), but this doesn't
+ * seem to work in testing.
+ */
+ SSL_clear_num_renegotiations(port->ssl);
+
+ SSL_set_session_id_context(port->ssl, (void *) &SSL_context,
+ sizeof(SSL_context));
+ if (SSL_renegotiate(port->ssl) <= 0)
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL failure during renegotiation start")));
+ else
+ {
+ int retries;
+
+ /*
+ * A handshake can fail, so be prepared to retry it, but only
+ * a few times.
+ */
+ for (retries = 0;; retries++)
+ {
+ if (SSL_do_handshake(port->ssl) > 0)
+ break; /* done */
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL handshake failure on renegotiation, retrying")));
+ if (retries >= 20)
+ ereport(FATAL,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("unable to complete SSL handshake")));
+ }
+ }
+ }
+
+wloop:
+ errno = 0;
+ n = SSL_write(port->ssl, ptr, len);
+ err = SSL_get_error(port->ssl, n);
+ switch (err)
+ {
+ case SSL_ERROR_NONE:
+ port->count += n;
+ break;
+ case SSL_ERROR_WANT_READ:
+ case SSL_ERROR_WANT_WRITE:
+#ifdef WIN32
+ pgwin32_waitforsinglesocket(SSL_get_fd(port->ssl),
+ (err == SSL_ERROR_WANT_READ) ?
+ FD_READ | FD_CLOSE : FD_WRITE | FD_CLOSE,
+ INFINITE);
+#endif
+ goto wloop;
+ case SSL_ERROR_SYSCALL:
+ /* leave it to caller to ereport the value of errno */
+ if (n != -1)
+ {
+ errno = ECONNRESET;
+ n = -1;
+ }
+ break;
+ case SSL_ERROR_SSL:
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL error: %s", SSLerrmessage())));
+ /* fall through */
+ case SSL_ERROR_ZERO_RETURN:
+ errno = ECONNRESET;
+ n = -1;
+ break;
+ default:
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("unrecognized SSL error code: %d",
+ err)));
+ errno = ECONNRESET;
+ n = -1;
+ break;
+ }
+
+ if (n >= 0)
+ {
+ /* is renegotiation complete? */
+ if (in_ssl_renegotiation &&
+ SSL_num_renegotiations(port->ssl) >= 1)
+ {
+ in_ssl_renegotiation = false;
+ port->count = 0;
+ }
+
+ /*
+ * if renegotiation is still ongoing, and we've gone beyond the
+ * limit, kill the connection now -- continuing to use it can be
+ * considered a security problem.
+ */
+ if (in_ssl_renegotiation &&
+ port->count > ssl_renegotiation_limit * 1024L)
+ ereport(FATAL,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL failed to renegotiate connection before limit expired")));
+ }
+
+ return n;
+}
+
+/* ------------------------------------------------------------ */
+/* OpenSSL specific code */
+/* ------------------------------------------------------------ */
+
+/*
+ * Private substitute BIO: this does the sending and receiving using send() and
+ * recv() instead. This is so that we can enable and disable interrupts
+ * just while calling recv(). We cannot have interrupts occurring while
+ * the bulk of openssl runs, because it uses malloc() and possibly other
+ * non-reentrant libc facilities. We also need to call send() and recv()
+ * directly so it gets passed through the socket/signals layer on Win32.
+ *
+ * These functions are closely modelled on the standard socket BIO in OpenSSL;
+ * see sock_read() and sock_write() in OpenSSL's crypto/bio/bss_sock.c.
+ * XXX OpenSSL 1.0.1e considers many more errcodes than just EINTR as reasons
+ * to retry; do we need to adopt their logic for that?
+ */
+
+static bool my_bio_initialized = false;
+static BIO_METHOD my_bio_methods;
+
+static int
+my_sock_read(BIO *h, char *buf, int size)
+{
+ int res = 0;
+
+ if (buf != NULL)
+ {
+ res = secure_raw_read(((Port *)h->ptr), buf, size);
+ BIO_clear_retry_flags(h);
+ if (res <= 0)
+ {
+ /* If we were interrupted, tell caller to retry */
+ if (errno == EINTR)
+ {
+ BIO_set_retry_read(h);
+ }
+ }
+ }
+
+ return res;
+}
+
+static int
+my_sock_write(BIO *h, const char *buf, int size)
+{
+ int res = 0;
+
+ res = secure_raw_write(((Port *) h->ptr), buf, size);
+ BIO_clear_retry_flags(h);
+ if (res <= 0)
+ {
+ if (errno == EINTR)
+ {
+ BIO_set_retry_write(h);
+ }
+ }
+
+ return res;
+}
+
+static BIO_METHOD *
+my_BIO_s_socket(void)
+{
+ if (!my_bio_initialized)
+ {
+ memcpy(&my_bio_methods, BIO_s_socket(), sizeof(BIO_METHOD));
+ my_bio_methods.bread = my_sock_read;
+ my_bio_methods.bwrite = my_sock_write;
+ my_bio_initialized = true;
+ }
+ return &my_bio_methods;
+}
+
+/* This should exactly match openssl's SSL_set_fd except for using my BIO */
+static int
+my_SSL_set_fd(Port *port, int fd)
+{
+ int ret = 0;
+ BIO *bio = NULL;
+
+ bio = BIO_new(my_BIO_s_socket());
+
+ if (bio == NULL)
+ {
+ SSLerr(SSL_F_SSL_SET_FD, ERR_R_BUF_LIB);
+ goto err;
+ }
+ /* Use 'ptr' to store pointer to PGconn */
+ bio->ptr = port;
+
+ BIO_set_fd(bio, fd, BIO_NOCLOSE);
+ SSL_set_bio(port->ssl, bio, bio);
+ ret = 1;
+err:
+ return ret;
+}
+
+/*
+ * Load precomputed DH parameters.
+ *
+ * To prevent "downgrade" attacks, we perform a number of checks
+ * to verify that the DBA-generated DH parameters file contains
+ * what we expect it to contain.
+ */
+static DH *
+load_dh_file(int keylength)
+{
+ FILE *fp;
+ char fnbuf[MAXPGPATH];
+ DH *dh = NULL;
+ int codes;
+
+ /* attempt to open file. It's not an error if it doesn't exist. */
+ snprintf(fnbuf, sizeof(fnbuf), "dh%d.pem", keylength);
+ if ((fp = fopen(fnbuf, "r")) == NULL)
+ return NULL;
+
+/* flock(fileno(fp), LOCK_SH); */
+ dh = PEM_read_DHparams(fp, NULL, NULL, NULL);
+/* flock(fileno(fp), LOCK_UN); */
+ fclose(fp);
+
+ /* is the prime the correct size? */
+ if (dh != NULL && 8 * DH_size(dh) < keylength)
+ {
+ elog(LOG, "DH errors (%s): %d bits expected, %d bits found",
+ fnbuf, keylength, 8 * DH_size(dh));
+ dh = NULL;
+ }
+
+ /* make sure the DH parameters are usable */
+ if (dh != NULL)
+ {
+ if (DH_check(dh, &codes) == 0)
+ {
+ elog(LOG, "DH_check error (%s): %s", fnbuf, SSLerrmessage());
+ return NULL;
+ }
+ if (codes & DH_CHECK_P_NOT_PRIME)
+ {
+ elog(LOG, "DH error (%s): p is not prime", fnbuf);
+ return NULL;
+ }
+ if ((codes & DH_NOT_SUITABLE_GENERATOR) &&
+ (codes & DH_CHECK_P_NOT_SAFE_PRIME))
+ {
+ elog(LOG,
+ "DH error (%s): neither suitable generator or safe prime",
+ fnbuf);
+ return NULL;
+ }
+ }
+
+ return dh;
+}
+
+/*
+ * Load hardcoded DH parameters.
+ *
+ * To prevent problems if the DH parameters files don't even
+ * exist, we can load DH parameters hardcoded into this file.
+ */
+static DH *
+load_dh_buffer(const char *buffer, size_t len)
+{
+ BIO *bio;
+ DH *dh = NULL;
+
+ bio = BIO_new_mem_buf((char *) buffer, len);
+ if (bio == NULL)
+ return NULL;
+ dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
+ if (dh == NULL)
+ ereport(DEBUG2,
+ (errmsg_internal("DH load buffer: %s",
+ SSLerrmessage())));
+ BIO_free(bio);
+
+ return dh;
+}
+
+/*
+ * Generate an ephemeral DH key. Because this can take a long
+ * time to compute, we can use precomputed parameters of the
+ * common key sizes.
+ *
+ * Since few sites will bother to precompute these parameter
+ * files, we also provide a fallback to the parameters provided
+ * by the OpenSSL project.
+ *
+ * These values can be static (once loaded or computed) since
+ * the OpenSSL library can efficiently generate random keys from
+ * the information provided.
+ */
+static DH *
+tmp_dh_cb(SSL *s, int is_export, int keylength)
+{
+ DH *r = NULL;
+ static DH *dh = NULL;
+ static DH *dh512 = NULL;
+ static DH *dh1024 = NULL;
+ static DH *dh2048 = NULL;
+ static DH *dh4096 = NULL;
+
+ switch (keylength)
+ {
+ case 512:
+ if (dh512 == NULL)
+ dh512 = load_dh_file(keylength);
+ if (dh512 == NULL)
+ dh512 = load_dh_buffer(file_dh512, sizeof file_dh512);
+ r = dh512;
+ break;
+
+ case 1024:
+ if (dh1024 == NULL)
+ dh1024 = load_dh_file(keylength);
+ if (dh1024 == NULL)
+ dh1024 = load_dh_buffer(file_dh1024, sizeof file_dh1024);
+ r = dh1024;
+ break;
+
+ case 2048:
+ if (dh2048 == NULL)
+ dh2048 = load_dh_file(keylength);
+ if (dh2048 == NULL)
+ dh2048 = load_dh_buffer(file_dh2048, sizeof file_dh2048);
+ r = dh2048;
+ break;
+
+ case 4096:
+ if (dh4096 == NULL)
+ dh4096 = load_dh_file(keylength);
+ if (dh4096 == NULL)
+ dh4096 = load_dh_buffer(file_dh4096, sizeof file_dh4096);
+ r = dh4096;
+ break;
+
+ default:
+ if (dh == NULL)
+ dh = load_dh_file(keylength);
+ r = dh;
+ }
+
+ /* this may take a long time, but it may be necessary... */
+ if (r == NULL || 8 * DH_size(r) < keylength)
+ {
+ ereport(DEBUG2,
+ (errmsg_internal("DH: generating parameters (%d bits)",
+ keylength)));
+ r = DH_generate_parameters(keylength, DH_GENERATOR_2, NULL, NULL);
+ }
+
+ 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;
+}
+
+/*
+ * This callback is used to copy SSL information messages
+ * into the PostgreSQL log.
+ */
+static void
+info_cb(const SSL *ssl, int type, int args)
+{
+ switch (type)
+ {
+ case SSL_CB_HANDSHAKE_START:
+ ereport(DEBUG4,
+ (errmsg_internal("SSL: handshake start")));
+ break;
+ case SSL_CB_HANDSHAKE_DONE:
+ ereport(DEBUG4,
+ (errmsg_internal("SSL: handshake done")));
+ break;
+ case SSL_CB_ACCEPT_LOOP:
+ ereport(DEBUG4,
+ (errmsg_internal("SSL: accept loop")));
+ break;
+ case SSL_CB_ACCEPT_EXIT:
+ ereport(DEBUG4,
+ (errmsg_internal("SSL: accept exit (%d)", args)));
+ break;
+ case SSL_CB_CONNECT_LOOP:
+ ereport(DEBUG4,
+ (errmsg_internal("SSL: connect loop")));
+ break;
+ case SSL_CB_CONNECT_EXIT:
+ ereport(DEBUG4,
+ (errmsg_internal("SSL: connect exit (%d)", args)));
+ break;
+ case SSL_CB_READ_ALERT:
+ ereport(DEBUG4,
+ (errmsg_internal("SSL: read alert (0x%04x)", args)));
+ break;
+ case SSL_CB_WRITE_ALERT:
+ ereport(DEBUG4,
+ (errmsg_internal("SSL: write alert (0x%04x)", args)));
+ break;
+ }
+}
+
+#if (OPENSSL_VERSION_NUMBER >= 0x0090800fL) && !defined(OPENSSL_NO_ECDH)
+static void
+initialize_ecdh(void)
+{
+ EC_KEY *ecdh;
+ int nid;
+
+ nid = OBJ_sn2nid(SSLECDHCurve);
+ if (!nid)
+ ereport(FATAL,
+ (errmsg("ECDH: unrecognized curve name: %s", SSLECDHCurve)));
+
+ ecdh = EC_KEY_new_by_curve_name(nid);
+ if (!ecdh)
+ ereport(FATAL,
+ (errmsg("ECDH: could not create key")));
+
+ SSL_CTX_set_options(SSL_context, SSL_OP_SINGLE_ECDH_USE);
+ SSL_CTX_set_tmp_ecdh(SSL_context, ecdh);
+ EC_KEY_free(ecdh);
+}
+#else
+#define initialize_ecdh()
+#endif
+
+/*
+ * Initialize global SSL context.
+ */
+void
+be_tls_init(void)
+{
+ struct stat buf;
+
+ STACK_OF(X509_NAME) *root_cert_list = NULL;
+
+ if (!SSL_context)
+ {
+#if SSLEAY_VERSION_NUMBER >= 0x0907000L
+ OPENSSL_config(NULL);
+#endif
+ SSL_library_init();
+ SSL_load_error_strings();
+
+ /*
+ * We use SSLv23_method() because it can negotiate use of the highest
+ * mutually supported protocol version, while alternatives like
+ * TLSv1_2_method() permit only one specific version. Note that we
+ * don't actually allow SSL v2 or v3, only TLS protocols (see below).
+ */
+ SSL_context = SSL_CTX_new(SSLv23_method());
+ if (!SSL_context)
+ ereport(FATAL,
+ (errmsg("could not create SSL context: %s",
+ SSLerrmessage())));
+
+ /*
+ * Disable OpenSSL's moving-write-buffer sanity check, because it
+ * causes unnecessary failures in nonblocking send cases.
+ */
+ SSL_CTX_set_mode(SSL_context, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
+
+ /*
+ * Load and verify server's certificate and private key
+ */
+ if (SSL_CTX_use_certificate_chain_file(SSL_context,
+ ssl_cert_file) != 1)
+ ereport(FATAL,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load server certificate file \"%s\": %s",
+ ssl_cert_file, SSLerrmessage())));
+
+ if (stat(ssl_key_file, &buf) != 0)
+ ereport(FATAL,
+ (errcode_for_file_access(),
+ errmsg("could not access private key file \"%s\": %m",
+ ssl_key_file)));
+
+ /*
+ * Require no public access to key file.
+ *
+ * XXX temporarily suppress check when on Windows, because there may
+ * not be proper support for Unix-y file permissions. Need to think
+ * of a reasonable check to apply on Windows. (See also the data
+ * directory permission check in postmaster.c)
+ */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+ if (!S_ISREG(buf.st_mode) || buf.st_mode & (S_IRWXG | S_IRWXO))
+ ereport(FATAL,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("private key file \"%s\" has group or world access",
+ ssl_key_file),
+ errdetail("Permissions should be u=rw (0600) or less.")));
+#endif
+
+ if (SSL_CTX_use_PrivateKey_file(SSL_context,
+ ssl_key_file,
+ SSL_FILETYPE_PEM) != 1)
+ ereport(FATAL,
+ (errmsg("could not load private key file \"%s\": %s",
+ ssl_key_file, SSLerrmessage())));
+
+ if (SSL_CTX_check_private_key(SSL_context) != 1)
+ ereport(FATAL,
+ (errmsg("check of private key failed: %s",
+ SSLerrmessage())));
+ }
+
+ /* set up ephemeral DH keys, and disallow SSL v2/v3 while at it */
+ SSL_CTX_set_tmp_dh_callback(SSL_context, tmp_dh_cb);
+ SSL_CTX_set_options(SSL_context,
+ SSL_OP_SINGLE_DH_USE |
+ SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
+
+ /* set up ephemeral ECDH keys */
+ initialize_ecdh();
+
+ /* set up the allowed cipher list */
+ if (SSL_CTX_set_cipher_list(SSL_context, SSLCipherSuites) != 1)
+ elog(FATAL, "could not set the cipher list (no valid ciphers available)");
+
+ /* Let server choose order */
+ if (SSLPreferServerCiphers)
+ SSL_CTX_set_options(SSL_context, SSL_OP_CIPHER_SERVER_PREFERENCE);
+
+ /*
+ * Load CA store, so we can verify client certificates if needed.
+ */
+ if (ssl_ca_file[0])
+ {
+ if (SSL_CTX_load_verify_locations(SSL_context, ssl_ca_file, NULL) != 1 ||
+ (root_cert_list = SSL_load_client_CA_file(ssl_ca_file)) == NULL)
+ ereport(FATAL,
+ (errmsg("could not load root certificate file \"%s\": %s",
+ ssl_ca_file, SSLerrmessage())));
+ }
+
+ /*----------
+ * Load the Certificate Revocation List (CRL).
+ * http://searchsecurity.techtarget.com/sDefinition/0,,sid14_gci803160,00.html
+ *----------
+ */
+ if (ssl_crl_file[0])
+ {
+ X509_STORE *cvstore = SSL_CTX_get_cert_store(SSL_context);
+
+ if (cvstore)
+ {
+ /* Set the flags to check against the complete CRL chain */
+ if (X509_STORE_load_locations(cvstore, ssl_crl_file, NULL) == 1)
+ {
+ /* OpenSSL 0.96 does not support X509_V_FLAG_CRL_CHECK */
+#ifdef X509_V_FLAG_CRL_CHECK
+ X509_STORE_set_flags(cvstore,
+ X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL);
+#else
+ ereport(LOG,
+ (errmsg("SSL certificate revocation list file \"%s\" ignored",
+ ssl_crl_file),
+ errdetail("SSL library does not support certificate revocation lists.")));
+#endif
+ }
+ else
+ ereport(FATAL,
+ (errmsg("could not load SSL certificate revocation list file \"%s\": %s",
+ ssl_crl_file, SSLerrmessage())));
+ }
+ }
+
+ if (ssl_ca_file[0])
+ {
+ /*
+ * Always ask for SSL client cert, but don't fail if it's not
+ * presented. We might fail such connections later, depending on what
+ * we find in pg_hba.conf.
+ */
+ SSL_CTX_set_verify(SSL_context,
+ (SSL_VERIFY_PEER |
+ SSL_VERIFY_CLIENT_ONCE),
+ verify_cb);
+
+ /* Set flag to remember CA store is successfully loaded */
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * Tell OpenSSL to send the list of root certs we trust to clients in
+ * CertificateRequests. This lets a client with a keystore select the
+ * appropriate client certificate to send to us.
+ */
+ SSL_CTX_set_client_CA_list(SSL_context, root_cert_list);
+ }
+}
+
+/*
+ * Attempt to negotiate SSL connection.
+ */
+int
+be_tls_open_server(Port *port)
+{
+ int r;
+ int err;
+
+ Assert(!port->ssl);
+ Assert(!port->peer);
+
+ if (!(port->ssl = SSL_new(SSL_context)))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("could not initialize SSL connection: %s",
+ SSLerrmessage())));
+ be_tls_close(port);
+ return -1;
+ }
+ if (!my_SSL_set_fd(port, port->sock))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("could not set SSL socket: %s",
+ SSLerrmessage())));
+ be_tls_close(port);
+ return -1;
+ }
+ port->ssl_in_use = true;
+
+aloop:
+ r = SSL_accept(port->ssl);
+ if (r <= 0)
+ {
+ err = SSL_get_error(port->ssl, r);
+ switch (err)
+ {
+ case SSL_ERROR_WANT_READ:
+ case SSL_ERROR_WANT_WRITE:
+#ifdef WIN32
+ pgwin32_waitforsinglesocket(SSL_get_fd(port->ssl),
+ (err == SSL_ERROR_WANT_READ) ?
+ FD_READ | FD_CLOSE | FD_ACCEPT : FD_WRITE | FD_CLOSE,
+ INFINITE);
+#endif
+ goto aloop;
+ case SSL_ERROR_SYSCALL:
+ if (r < 0)
+ ereport(COMMERROR,
+ (errcode_for_socket_access(),
+ errmsg("could not accept SSL connection: %m")));
+ else
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("could not accept SSL connection: EOF detected")));
+ break;
+ case SSL_ERROR_SSL:
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("could not accept SSL connection: %s",
+ SSLerrmessage())));
+ break;
+ case SSL_ERROR_ZERO_RETURN:
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("could not accept SSL connection: EOF detected")));
+ break;
+ default:
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("unrecognized SSL error code: %d",
+ err)));
+ break;
+ }
+ be_tls_close(port);
+ return -1;
+ }
+
+ port->count = 0;
+
+ /* Get client certificate, if available. */
+ port->peer = SSL_get_peer_certificate(port->ssl);
+
+ /* and extract the Common Name from it. */
+ port->peer_cn = NULL;
+ port->peer_cert_valid = false;
+ if (port->peer != NULL)
+ {
+ int len;
+
+ len = X509_NAME_get_text_by_NID(X509_get_subject_name(port->peer),
+ NID_commonName, NULL, 0);
+ if (len != -1)
+ {
+ char *peer_cn;
+
+ peer_cn = MemoryContextAlloc(TopMemoryContext, len + 1);
+ r = X509_NAME_get_text_by_NID(X509_get_subject_name(port->peer),
+ NID_commonName, peer_cn, len + 1);
+ peer_cn[len] = '\0';
+ if (r != len)
+ {
+ /* shouldn't happen */
+ pfree(peer_cn);
+ be_tls_close(port);
+ return -1;
+ }
+
+ /*
+ * Reject embedded NULLs in certificate common name to prevent
+ * attacks like CVE-2009-4034.
+ */
+ if (len != strlen(peer_cn))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL certificate's common name contains embedded null")));
+ pfree(peer_cn);
+ be_tls_close(port);
+ return -1;
+ }
+
+ port->peer_cn = peer_cn;
+ }
+ port->peer_cert_valid = true;
+ }
+
+ ereport(DEBUG2,
+ (errmsg("SSL connection from \"%s\"",
+ port->peer_cn ? port->peer_cn : "(anonymous)")));
+
+ /* set up debugging/info callback */
+ SSL_CTX_set_info_callback(SSL_context, info_cb);
+
+ return 0;
+}
+
+/*
+ * Close SSL connection.
+ */
+void
+be_tls_close(Port *port)
+{
+ if (port->ssl)
+ {
+ SSL_shutdown(port->ssl);
+ SSL_free(port->ssl);
+ port->ssl = NULL;
+ port->ssl_in_use = false;
+ }
+
+ if (port->peer)
+ {
+ X509_free(port->peer);
+ port->peer = NULL;
+ }
+
+ if (port->peer_cn)
+ {
+ pfree(port->peer_cn);
+ port->peer_cn = NULL;
+ }
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len)
+{
+ ssize_t n;
+ int err;
+
+rloop:
+ errno = 0;
+ n = SSL_read(port->ssl, ptr, len);
+ err = SSL_get_error(port->ssl, n);
+ switch (err)
+ {
+ case SSL_ERROR_NONE:
+ port->count += n;
+ break;
+ case SSL_ERROR_WANT_READ:
+ case SSL_ERROR_WANT_WRITE:
+ if (port->noblock)
+ {
+ errno = EWOULDBLOCK;
+ n = -1;
+ break;
+ }
+#ifdef WIN32
+ pgwin32_waitforsinglesocket(SSL_get_fd(port->ssl),
+ (err == SSL_ERROR_WANT_READ) ?
+ FD_READ | FD_CLOSE : FD_WRITE | FD_CLOSE,
+ INFINITE);
+#endif
+ goto rloop;
+ case SSL_ERROR_SYSCALL:
+ /* leave it to caller to ereport the value of errno */
+ if (n != -1)
+ {
+ errno = ECONNRESET;
+ n = -1;
+ }
+ break;
+ case SSL_ERROR_SSL:
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL error: %s", SSLerrmessage())));
+ /* fall through */
+ case SSL_ERROR_ZERO_RETURN:
+ errno = ECONNRESET;
+ n = -1;
+ break;
+ default:
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("unrecognized SSL error code: %d",
+ err)));
+ errno = ECONNRESET;
+ n = -1;
+ break;
+ }
+
+ return n;
+}
+
+/*
+ * 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;
+}
+