diff options
author | Heikki Linnakangas <heikki.linnakangas@iki.fi> | 2014-08-11 11:54:19 +0300 |
---|---|---|
committer | Heikki Linnakangas <heikki.linnakangas@iki.fi> | 2014-08-11 11:54:19 +0300 |
commit | 680513ab79c7e12e402a2aad7921b95a25a4bcc8 (patch) | |
tree | c2a5b1debb5599ae4a3522be921a78a6f1cf35c3 /src/backend/libpq/be-secure-openssl.c | |
parent | 6aa61580e08d58909b2a8845a4087b7699335ee0 (diff) | |
download | postgresql-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.c | 1045 |
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; +} + |