diff options
Diffstat (limited to 'src/backend/libpq')
-rw-r--r-- | src/backend/libpq/Makefile | 4 | ||||
-rw-r--r-- | src/backend/libpq/auth.c | 14 | ||||
-rw-r--r-- | src/backend/libpq/be-secure-openssl.c | 1045 | ||||
-rw-r--r-- | src/backend/libpq/be-secure.c | 1027 | ||||
-rw-r--r-- | src/backend/libpq/hba.c | 2 |
5 files changed, 1081 insertions, 1011 deletions
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile index e9298646462..8be0572b5b7 100644 --- a/src/backend/libpq/Makefile +++ b/src/backend/libpq/Makefile @@ -17,4 +17,8 @@ include $(top_builddir)/src/Makefile.global OBJS = be-fsstubs.o be-secure.o auth.o crypt.o hba.o ip.o md5.o pqcomm.o \ pqformat.o pqsignal.o +ifeq ($(with_openssl),yes) +OBJS += be-secure-openssl.o +endif + include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index 70b0b939823..b1974d121cd 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -161,7 +161,7 @@ static int pg_SSPI_recvauth(Port *port); * RADIUS Authentication *---------------------------------------------------------------- */ -#ifdef USE_SSL +#ifdef USE_OPENSSL #include <openssl/rand.h> #endif static int CheckRADIUSAuth(Port *port); @@ -330,7 +330,7 @@ ClientAuthentication(Port *port) * already if it didn't verify ok. */ #ifdef USE_SSL - if (!port->peer) + if (!port->peer_cert_valid) { ereport(FATAL, (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), @@ -378,7 +378,7 @@ ClientAuthentication(Port *port) (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), errmsg("pg_hba.conf rejects replication connection for host \"%s\", user \"%s\", %s", hostinfo, port->user_name, - port->ssl ? _("SSL on") : _("SSL off")))); + port->ssl_in_use ? _("SSL on") : _("SSL off")))); #else ereport(FATAL, (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), @@ -394,7 +394,7 @@ ClientAuthentication(Port *port) errmsg("pg_hba.conf rejects connection for host \"%s\", user \"%s\", database \"%s\", %s", hostinfo, port->user_name, port->database_name, - port->ssl ? _("SSL on") : _("SSL off")))); + port->ssl_in_use ? _("SSL on") : _("SSL off")))); #else ereport(FATAL, (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), @@ -452,7 +452,7 @@ ClientAuthentication(Port *port) (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), errmsg("no pg_hba.conf entry for replication connection from host \"%s\", user \"%s\", %s", hostinfo, port->user_name, - port->ssl ? _("SSL on") : _("SSL off")), + port->ssl_in_use ? _("SSL on") : _("SSL off")), HOSTNAME_LOOKUP_DETAIL(port))); #else ereport(FATAL, @@ -470,7 +470,7 @@ ClientAuthentication(Port *port) errmsg("no pg_hba.conf entry for host \"%s\", user \"%s\", database \"%s\", %s", hostinfo, port->user_name, port->database_name, - port->ssl ? _("SSL on") : _("SSL off")), + port->ssl_in_use ? _("SSL on") : _("SSL off")), HOSTNAME_LOOKUP_DETAIL(port))); #else ereport(FATAL, @@ -2315,7 +2315,7 @@ CheckRADIUSAuth(Port *port) /* Construct RADIUS packet */ packet->code = RADIUS_ACCESS_REQUEST; packet->length = RADIUS_HEADER_LENGTH; -#ifdef USE_SSL +#ifdef USE_OPENSSL if (RAND_bytes(packet->vector, RADIUS_VECTOR_LENGTH) != 1) { ereport(LOG, 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; +} + diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c index 59204cfe801..41ec1ad8ad9 100644 --- a/src/backend/libpq/be-secure.c +++ b/src/backend/libpq/be-secure.c @@ -13,38 +13,6 @@ * IDENTIFICATION * src/backend/libpq/be-secure.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. - * *------------------------------------------------------------------------- */ @@ -63,35 +31,11 @@ #include <arpa/inet.h> #endif -#ifdef USE_SSL -#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 -#endif /* USE_SSL */ - #include "libpq/libpq.h" #include "tcop/tcopprot.h" #include "utils/memutils.h" -#ifdef USE_SSL - -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 void initialize_SSL(void); -static int open_server_SSL(Port *); -static void close_SSL(Port *); -static const char *SSLerrmessage(void); -#endif - char *ssl_cert_file; char *ssl_key_file; char *ssl_ca_file; @@ -105,11 +49,7 @@ char *ssl_crl_file; int ssl_renegotiation_limit; #ifdef USE_SSL -/* are we in the middle of a renegotiation? */ -static bool in_ssl_renegotiation = false; - -static SSL_CTX *SSL_context = NULL; -static bool ssl_loaded_verify_locations = false; +bool ssl_loaded_verify_locations = false; #endif /* GUC variable controlling SSL cipher list */ @@ -122,73 +62,6 @@ char *SSLECDHCurve; bool SSLPreferServerCiphers; /* ------------------------------------------------------------ */ -/* 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. - */ -#ifdef USE_SSL - -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"; -#endif - -/* ------------------------------------------------------------ */ /* Procedures common to all secure sessions */ /* ------------------------------------------------------------ */ @@ -199,7 +72,7 @@ int secure_initialize(void) { #ifdef USE_SSL - initialize_SSL(); + be_tls_init(); #endif return 0; @@ -227,7 +100,7 @@ secure_open_server(Port *port) int r = 0; #ifdef USE_SSL - r = open_server_SSL(port); + r = be_tls_open_server(port); #endif return r; @@ -240,8 +113,8 @@ void secure_close(Port *port) { #ifdef USE_SSL - if (port->ssl) - close_SSL(port); + if (port->ssl_in_use) + be_tls_close(port); #endif } @@ -254,908 +127,56 @@ secure_read(Port *port, void *ptr, size_t len) ssize_t n; #ifdef USE_SSL - if (port->ssl) + if (port->ssl_in_use) { - 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; - } + n = be_tls_read(port, ptr, len); } else #endif { - prepare_for_client_read(); - - n = recv(port->sock, ptr, len, 0); - - client_read_ended(); + n = secure_raw_read(port, ptr, len); } return n; } -/* - * Write data to a secure connection. - */ ssize_t -secure_write(Port *port, void *ptr, size_t len) +secure_raw_read(Port *port, void *ptr, size_t len) { ssize_t n; -#ifdef USE_SSL - if (port->ssl) - { - 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"))); - } - } - else -#endif - n = send(port->sock, ptr, len, 0); - - return n; -} - -/* ------------------------------------------------------------ */ -/* SSL specific code */ -/* ------------------------------------------------------------ */ -#ifdef USE_SSL - -/* - * 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; - prepare_for_client_read(); - if (buf != NULL) - { - res = recv(h->num, buf, size, 0); - BIO_clear_retry_flags(h); - if (res <= 0) - { - /* If we were interrupted, tell caller to retry */ - if (errno == EINTR) - { - BIO_set_retry_read(h); - } - } - } + n = recv(port->sock, ptr, len, 0); client_read_ended(); - return res; -} - -static int -my_sock_write(BIO *h, const char *buf, int size) -{ - int res = 0; - - res = send(h->num, buf, size, 0); - 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(SSL *s, 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; - } - BIO_set_fd(bio, fd, BIO_NOCLOSE); - SSL_set_bio(s, 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; - } + return n; } -#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. - */ -static void -initialize_SSL(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. + * Write data to a secure connection. */ -static int -open_server_SSL(Port *port) +ssize_t +secure_write(Port *port, void *ptr, size_t len) { - int r; - int err; - - Assert(!port->ssl); - Assert(!port->peer); + ssize_t n; - if (!(port->ssl = SSL_new(SSL_context))) - { - ereport(COMMERROR, - (errcode(ERRCODE_PROTOCOL_VIOLATION), - errmsg("could not initialize SSL connection: %s", - SSLerrmessage()))); - close_SSL(port); - return -1; - } - if (!my_SSL_set_fd(port->ssl, port->sock)) +#ifdef USE_SSL + if (port->ssl_in_use) { - ereport(COMMERROR, - (errcode(ERRCODE_PROTOCOL_VIOLATION), - errmsg("could not set SSL socket: %s", - SSLerrmessage()))); - close_SSL(port); - return -1; + n = be_tls_write(port, ptr, len); } - -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); + else #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; - } - close_SSL(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; - if (port->peer != NULL) - { - int len; + n = secure_raw_write(port, ptr, 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); - close_SSL(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); - close_SSL(port); - return -1; - } - - port->peer_cn = peer_cn; - } - } - - 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. - */ -static void -close_SSL(Port *port) -{ - if (port->ssl) - { - SSL_shutdown(port->ssl); - SSL_free(port->ssl); - port->ssl = NULL; - } - - if (port->peer) - { - X509_free(port->peer); - port->peer = NULL; - } - - if (port->peer_cn) - { - pfree(port->peer_cn); - port->peer_cn = NULL; - } + 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) +ssize_t +secure_raw_write(Port *port, const void *ptr, size_t len) { - 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 send(port->sock, ptr, len, 0); } - -#endif /* USE_SSL */ diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index fd98c60ddb0..84da823ffab 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -1685,7 +1685,7 @@ check_hba(hbaPort *port) /* Check SSL state */ #ifdef USE_SSL - if (port->ssl) + if (port->ssl_in_use) { /* Connection is SSL, match both "host" and "hostssl" */ if (hba->conntype == ctHostNoSSL) |