Commit 680513ab authored by Heikki Linnakangas's avatar Heikki Linnakangas

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.
parent 6aa61580
......@@ -5492,7 +5492,7 @@ if test "${with_openssl+set}" = set; then :
case $withval in
yes)
$as_echo "#define USE_SSL 1" >>confdefs.h
$as_echo "#define USE_OPENSSL 1" >>confdefs.h
;;
no)
......
......@@ -657,7 +657,7 @@ AC_MSG_RESULT([$with_bonjour])
#
AC_MSG_CHECKING([whether to build with OpenSSL support])
PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
[AC_DEFINE([USE_SSL], 1, [Define to build with (Open)SSL support. (--with-openssl)])])
[AC_DEFINE([USE_OPENSSL], 1, [Define to build with OpenSSL support. (--with-openssl)])])
AC_MSG_RESULT([$with_openssl])
AC_SUBST(with_openssl)
......
......@@ -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
......@@ -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,
......
/*-------------------------------------------------------------------------
*
* 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;
}
......@@ -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 */
......@@ -121,73 +61,6 @@ char *SSLECDHCurve;
/* GUC variable: if false, prefer client ciphers */
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)
{
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)
if (port->ssl_in_use)
{
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.
* Write data to a secure connection.
*/
static void
initialize_SSL(void)
ssize_t
secure_write(Port *port, void *ptr, size_t len)
{
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);
ssize_t n;
if (cvstore)
{
/* Set the flags to check against the complete CRL chain */
if (X509_STORE_load_locations(cvstore, ssl_crl_file, NULL) == 1)
#ifdef USE_SSL
if (port->ssl_in_use)
{
/* 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
n = be_tls_write(port, ptr, len);
}
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.
*/
static int
open_server_SSL(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())));
close_SSL(port);
return -1;
}
if (!my_SSL_set_fd(port->ssl, port->sock))
{
ereport(COMMERROR,
(errcode(ERRCODE_PROTOCOL_VIOLATION),
errmsg("could not set SSL socket: %s",
SSLerrmessage())));
close_SSL(port);
return -1;
}
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;
}
close_SSL(port);
return -1;
}
port->count = 0;
n = secure_raw_write(port, ptr, len);
/* 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;
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 */
......@@ -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)
......
......@@ -17,7 +17,7 @@
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>
#ifdef USE_SSL
#ifdef USE_OPENSSL
#include <openssl/rand.h>
#endif
......@@ -110,7 +110,7 @@ fork_process(void)
/*
* Make sure processes do not share OpenSSL randomness state.
*/
#ifdef USE_SSL
#ifdef USE_OPENSSL
RAND_cleanup();
#endif
}
......
......@@ -231,8 +231,8 @@ PerformAuthentication(Port *port)
{
if (am_walsender)
{
#ifdef USE_SSL
if (port->ssl)
#ifdef USE_OPENSSL
if (port->ssl_in_use)
ereport(LOG,
(errmsg("replication connection authorized: user=%s SSL enabled (protocol=%s, cipher=%s, compression=%s)",
port->user_name, SSL_get_version(port->ssl), SSL_get_cipher(port->ssl),
......@@ -245,8 +245,8 @@ PerformAuthentication(Port *port)
}
else
{
#ifdef USE_SSL
if (port->ssl)
#ifdef USE_OPENSSL
if (port->ssl_in_use)
ereport(LOG,
(errmsg("connection authorized: user=%s database=%s SSL enabled (protocol=%s, cipher=%s, compression=%s)",
port->user_name, port->database_name, SSL_get_version(port->ssl), SSL_get_cipher(port->ssl),
......
......@@ -125,9 +125,6 @@ extern char *default_tablespace;
extern char *temp_tablespaces;
extern bool ignore_checksum_failure;
extern bool synchronize_seqscans;
extern char *SSLCipherSuites;
extern char *SSLECDHCurve;
extern bool SSLPreferServerCiphers;
#ifdef TRACE_SORT
extern bool trace_sort;
......
......@@ -30,7 +30,7 @@
#include <sys/types.h> /* for umask() */
#include <sys/stat.h> /* for stat() */
#endif
#ifdef USE_SSL
#ifdef USE_OPENSSL
#include <openssl/ssl.h>
#endif
......@@ -1791,7 +1791,7 @@ connection_warnings(bool in_startup)
static void
printSSLInfo(void)
{
#ifdef USE_SSL
#ifdef USE_OPENSSL
int sslbits = -1;
SSL *ssl;
......
......@@ -21,7 +21,7 @@
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef USE_SSL
#ifdef USE_OPENSSL
#include <openssl/ssl.h>
#include <openssl/err.h>
#endif
......@@ -184,17 +184,33 @@ typedef struct Port
#endif
/*
* SSL structures (keep these last so that USE_SSL doesn't affect
* locations of other fields)
* SSL structures (keep these last so that the locations of other fields
* are the same whether or not you build with SSL)
*/
#ifdef USE_SSL
bool ssl_in_use;
char *peer_cn;
bool peer_cert_valid;
#endif
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
char *peer_cn;
unsigned long count;
#endif
} Port;
#ifdef USE_SSL
/*
* These functions are implemented by the glue code specific to each
* SSL implementation (e.g. be-secure-openssl.c)
*/
extern void be_tls_init(void);
extern int be_tls_open_server(Port *port);
extern void be_tls_close(Port *port);
extern ssize_t be_tls_read(Port *port, void *ptr, size_t len);
extern ssize_t be_tls_write(Port *port, void *ptr, size_t len);
#endif
extern ProtocolVersion FrontendProtocol;
......
......@@ -82,5 +82,14 @@ extern int secure_open_server(Port *port);
extern void secure_close(Port *port);
extern ssize_t secure_read(Port *port, void *ptr, size_t len);
extern ssize_t secure_write(Port *port, void *ptr, size_t len);
extern ssize_t secure_raw_read(Port *port, void *ptr, size_t len);
extern ssize_t secure_raw_write(Port *port, const void *ptr, size_t len);
extern bool ssl_loaded_verify_locations;
/* GUCs */
extern char *SSLCipherSuites;
extern char *SSLECDHCurve;
extern bool SSLPreferServerCiphers;
#endif /* LIBPQ_H */
......@@ -778,15 +778,15 @@
/* Define to select named POSIX semaphores. */
#undef USE_NAMED_POSIX_SEMAPHORES
/* Define to build with OpenSSL support. (--with-openssl) */
#undef USE_OPENSSL
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
/* Use replacement snprintf() functions. */
#undef USE_REPL_SNPRINTF
/* Define to build with (Open)SSL support. (--with-openssl) */
#undef USE_SSL
/* Define to select SysV-style semaphores. */
#undef USE_SYSV_SEMAPHORES
......
......@@ -628,15 +628,15 @@
/* Define to select named POSIX semaphores. */
/* #undef USE_NAMED_POSIX_SEMAPHORES */
/* Define to build with OpenSSL support. (--with-openssl) */
/* #undef USE_OPENSSL */
/* Define to 1 to build with PAM support. (--with-pam) */
/* #undef USE_PAM */
/* Use replacement snprintf() functions. */
#define USE_REPL_SNPRINTF 1
/* Define to build with (Open)SSL support. (--with-openssl) */
/* #undef USE_SSL */
/* Define to select SysV-style semaphores. */
/* #undef USE_SYSV_SEMAPHORES */
......
......@@ -144,6 +144,15 @@
#define USE_PREFETCH
#endif
/*
* USE_SSL code should be compiled only when compiling with an SSL
* implementation. (Currently, only OpenSSL is supported, but we might add
* more implementations in the future.)
*/
#ifdef USE_OPENSSL
#define USE_SSL
#endif
/*
* This is the default directory in which AF_UNIX socket files are
* placed. Caution: changing this risks breaking your existing client
......
......@@ -44,6 +44,10 @@ OBJS += ip.o md5.o
# utils/mb
OBJS += encnames.o wchar.o
ifeq ($(with_openssl),yes)
OBJS += fe-secure-openssl.o
endif
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
endif
......
......@@ -1961,7 +1961,7 @@ keep_going: /* We will come back to here until there is
conn->allow_ssl_try = false;
}
if (conn->allow_ssl_try && !conn->wait_ssl_try &&
conn->ssl == NULL)
!conn->ssl_in_use)
{
ProtocolVersion pv;
......@@ -2040,7 +2040,7 @@ keep_going: /* We will come back to here until there is
* On first time through, get the postmaster's response to our
* SSL negotiation packet.
*/
if (conn->ssl == NULL)
if (!conn->ssl_in_use)
{
/*
* We use pqReadData here since it has the logic to
......@@ -2310,7 +2310,7 @@ keep_going: /* We will come back to here until there is
* connection already, then retry with an SSL connection
*/
if (conn->sslmode[0] == 'a' /* "allow" */
&& conn->ssl == NULL
&& !conn->ssl_in_use
&& conn->allow_ssl_try
&& conn->wait_ssl_try)
{
......@@ -2709,6 +2709,7 @@ makeEmptyPGconn(void)
#ifdef USE_SSL
conn->allow_ssl_try = true;
conn->wait_ssl_try = false;
conn->ssl_in_use = false;
#endif
/*
......
......@@ -751,7 +751,7 @@ retry3:
*/
#ifdef USE_SSL
if (conn->ssl)
if (conn->ssl_in_use)
return 0;
#endif
......@@ -1051,7 +1051,7 @@ pqSocketCheck(PGconn *conn, int forRead, int forWrite, time_t end_time)
return -1;
}
#ifdef USE_SSL
#ifdef USE_OPENSSL
/* Check for SSL library buffering read bytes */
if (forRead && conn->ssl && SSL_pending(conn->ssl) > 0)
{
......
/*-------------------------------------------------------------------------
*
* fe-secure-openssl.c
* OpenSSL support
*
*
* Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* src/interfaces/libpq/fe-secure-openssl.c
*
* NOTES
*
* We don't provide informational callbacks here (like
* info_cb() in be-secure.c), since there's no good mechanism to
* display such information to the user.
*
*-------------------------------------------------------------------------
*/
#include "postgres_fe.h"
#include <signal.h>
#include <fcntl.h>
#include <ctype.h>
#include "libpq-fe.h"
#include "fe-auth.h"
#include "libpq-int.h"
#ifdef WIN32
#include "win32.h"
#else
#include <sys/socket.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h>
#endif
#include <arpa/inet.h>
#endif
#include <sys/stat.h>
#ifdef ENABLE_THREAD_SAFETY
#ifdef WIN32
#include "pthread-win32.h"
#else
#include <pthread.h>
#endif
#endif
#include <openssl/ssl.h>
#if (SSLEAY_VERSION_NUMBER >= 0x00907000L)
#include <openssl/conf.h>
#endif
#ifdef USE_SSL_ENGINE
#include <openssl/engine.h>
#endif
static bool verify_peer_name_matches_certificate(PGconn *);
static int verify_cb(int ok, X509_STORE_CTX *ctx);
static void destroy_ssl_system(void);
static int initialize_SSL(PGconn *conn);
static PostgresPollingStatusType open_client_SSL(PGconn *);
static char *SSLerrmessage(void);
static void SSLerrfree(char *buf);
static int my_sock_read(BIO *h, char *buf, int size);
static int my_sock_write(BIO *h, const char *buf, int size);
static BIO_METHOD *my_BIO_s_socket(void);
static int my_SSL_set_fd(PGconn *conn, int fd);
static bool pq_init_ssl_lib = true;
static bool pq_init_crypto_lib = true;
/*
* SSL_context is currently shared between threads and therefore we need to be
* careful to lock around any usage of it when providing thread safety.
* ssl_config_mutex is the mutex that we use to protect it.
*/
static SSL_CTX *SSL_context = NULL;
#ifdef ENABLE_THREAD_SAFETY
static long ssl_open_connections = 0;
#ifndef WIN32
static pthread_mutex_t ssl_config_mutex = PTHREAD_MUTEX_INITIALIZER;
#else
static pthread_mutex_t ssl_config_mutex = NULL;
static long win32_ssl_create_mutex = 0;
#endif
#endif /* ENABLE_THREAD_SAFETY */
/* ------------------------------------------------------------ */
/* Procedures common to all secure sessions */
/* ------------------------------------------------------------ */
/*
* Exported function to allow application to tell us it's already
* initialized OpenSSL and/or libcrypto.
*/
void
pgtls_init_library(bool do_ssl, int do_crypto)
{
#ifdef ENABLE_THREAD_SAFETY
/*
* Disallow changing the flags while we have open connections, else we'd
* get completely confused.
*/
if (ssl_open_connections != 0)
return;
#endif
pq_init_ssl_lib = do_ssl;
pq_init_crypto_lib = do_crypto;
}
/*
* Begin or continue negotiating a secure session.
*/
PostgresPollingStatusType
pgtls_open_client(PGconn *conn)
{
/* First time through? */
if (conn->ssl == NULL)
{
#ifdef ENABLE_THREAD_SAFETY
int rc;
#endif
#ifdef ENABLE_THREAD_SAFETY
if ((rc = pthread_mutex_lock(&ssl_config_mutex)))
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not acquire mutex: %s\n"), strerror(rc));
return PGRES_POLLING_FAILED;
}
#endif
/* Create a connection-specific SSL object */
if (!(conn->ssl = SSL_new(SSL_context)) ||
!SSL_set_app_data(conn->ssl, conn) ||
!my_SSL_set_fd(conn, conn->sock))
{
char *err = SSLerrmessage();
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not establish SSL connection: %s\n"),
err);
SSLerrfree(err);
#ifdef ENABLE_THREAD_SAFETY
pthread_mutex_unlock(&ssl_config_mutex);
#endif
pgtls_close(conn);
return PGRES_POLLING_FAILED;
}
conn->ssl_in_use = true;
#ifdef ENABLE_THREAD_SAFETY
pthread_mutex_unlock(&ssl_config_mutex);
#endif
/*
* Load client certificate, private key, and trusted CA certs.
*/
if (initialize_SSL(conn) != 0)
{
/* initialize_SSL already put a message in conn->errorMessage */
pgtls_close(conn);
return PGRES_POLLING_FAILED;
}
}
/* Begin or continue the actual handshake */
return open_client_SSL(conn);
}
/*
* Read data from a secure connection.
*
* On failure, this function is responsible for putting a suitable message
* into conn->errorMessage. The caller must still inspect errno, but only
* to determine whether to continue/retry after error.
*/
ssize_t
pgtls_read(PGconn *conn, void *ptr, size_t len)
{
ssize_t n;
int result_errno = 0;
char sebuf[256];
int err;
rloop:
SOCK_ERRNO_SET(0);
n = SSL_read(conn->ssl, ptr, len);
err = SSL_get_error(conn->ssl, n);
switch (err)
{
case SSL_ERROR_NONE:
if (n < 0)
{
/* Not supposed to happen, so we don't translate the msg */
printfPQExpBuffer(&conn->errorMessage,
"SSL_read failed but did not provide error information\n");
/* assume the connection is broken */
result_errno = ECONNRESET;
}
break;
case SSL_ERROR_WANT_READ:
n = 0;
break;
case SSL_ERROR_WANT_WRITE:
/*
* Returning 0 here would cause caller to wait for read-ready,
* which is not correct since what SSL wants is wait for
* write-ready. The former could get us stuck in an infinite
* wait, so don't risk it; busy-loop instead.
*/
goto rloop;
case SSL_ERROR_SYSCALL:
if (n < 0)
{
result_errno = SOCK_ERRNO;
if (result_errno == EPIPE ||
result_errno == ECONNRESET)
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext(
"server closed the connection unexpectedly\n"
"\tThis probably means the server terminated abnormally\n"
"\tbefore or while processing the request.\n"));
else
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("SSL SYSCALL error: %s\n"),
SOCK_STRERROR(result_errno,
sebuf, sizeof(sebuf)));
}
else
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("SSL SYSCALL error: EOF detected\n"));
/* assume the connection is broken */
result_errno = ECONNRESET;
n = -1;
}
break;
case SSL_ERROR_SSL:
{
char *errm = SSLerrmessage();
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("SSL error: %s\n"), errm);
SSLerrfree(errm);
/* assume the connection is broken */
result_errno = ECONNRESET;
n = -1;
break;
}
case SSL_ERROR_ZERO_RETURN:
/*
* Per OpenSSL documentation, this error code is only returned
* for a clean connection closure, so we should not report it
* as a server crash.
*/
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("SSL connection has been closed unexpectedly\n"));
result_errno = ECONNRESET;
n = -1;
break;
default:
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("unrecognized SSL error code: %d\n"),
err);
/* assume the connection is broken */
result_errno = ECONNRESET;
n = -1;
break;
}
/* ensure we return the intended errno to caller */
SOCK_ERRNO_SET(result_errno);
return n;
}
/*
* Write data to a secure connection.
*
* On failure, this function is responsible for putting a suitable message
* into conn->errorMessage. The caller must still inspect errno, but only
* to determine whether to continue/retry after error.
*/
ssize_t
pgtls_write(PGconn *conn, const void *ptr, size_t len)
{
ssize_t n;
int result_errno = 0;
char sebuf[256];
int err;
SOCK_ERRNO_SET(0);
n = SSL_write(conn->ssl, ptr, len);
err = SSL_get_error(conn->ssl, n);
switch (err)
{
case SSL_ERROR_NONE:
if (n < 0)
{
/* Not supposed to happen, so we don't translate the msg */
printfPQExpBuffer(&conn->errorMessage,
"SSL_write failed but did not provide error information\n");
/* assume the connection is broken */
result_errno = ECONNRESET;
}
break;
case SSL_ERROR_WANT_READ:
/*
* Returning 0 here causes caller to wait for write-ready,
* which is not really the right thing, but it's the best we
* can do.
*/
n = 0;
break;
case SSL_ERROR_WANT_WRITE:
n = 0;
break;
case SSL_ERROR_SYSCALL:
if (n < 0)
{
result_errno = SOCK_ERRNO;
if (result_errno == EPIPE || result_errno == ECONNRESET)
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext(
"server closed the connection unexpectedly\n"
"\tThis probably means the server terminated abnormally\n"
"\tbefore or while processing the request.\n"));
else
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("SSL SYSCALL error: %s\n"),
SOCK_STRERROR(result_errno,
sebuf, sizeof(sebuf)));
}
else
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("SSL SYSCALL error: EOF detected\n"));
/* assume the connection is broken */
result_errno = ECONNRESET;
n = -1;
}
break;
case SSL_ERROR_SSL:
{
char *errm = SSLerrmessage();
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("SSL error: %s\n"), errm);
SSLerrfree(errm);
/* assume the connection is broken */
result_errno = ECONNRESET;
n = -1;
break;
}
case SSL_ERROR_ZERO_RETURN:
/*
* Per OpenSSL documentation, this error code is only returned
* for a clean connection closure, so we should not report it
* as a server crash.
*/
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("SSL connection has been closed unexpectedly\n"));
result_errno = ECONNRESET;
n = -1;
break;
default:
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("unrecognized SSL error code: %d\n"),
err);
/* assume the connection is broken */
result_errno = ECONNRESET;
n = -1;
break;
}
/* ensure we return the intended errno to caller */
SOCK_ERRNO_SET(result_errno);
return n;
}
/* ------------------------------------------------------------ */
/* OpenSSL specific code */
/* ------------------------------------------------------------ */
/*
* Certificate verification callback
*
* This callback allows us to log intermediate problems during
* verification, but there doesn't seem to be a clean way to get
* our PGconn * structure. So we can't log anything!
*
* 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;
}
/*
* Check if a wildcard certificate matches the server hostname.
*
* The rule for this is:
* 1. We only match the '*' character as wildcard
* 2. We match only wildcards at the start of the string
* 3. The '*' character does *not* match '.', meaning that we match only
* a single pathname component.
* 4. We don't support more than one '*' in a single pattern.
*
* This is roughly in line with RFC2818, but contrary to what most browsers
* appear to be implementing (point 3 being the difference)
*
* Matching is always case-insensitive, since DNS is case insensitive.
*/
static int
wildcard_certificate_match(const char *pattern, const char *string)
{
int lenpat = strlen(pattern);
int lenstr = strlen(string);
/* If we don't start with a wildcard, it's not a match (rule 1 & 2) */
if (lenpat < 3 ||
pattern[0] != '*' ||
pattern[1] != '.')
return 0;
if (lenpat > lenstr)
/* If pattern is longer than the string, we can never match */
return 0;
if (pg_strcasecmp(pattern + 1, string + lenstr - lenpat + 1) != 0)
/*
* If string does not end in pattern (minus the wildcard), we don't
* match
*/
return 0;
if (strchr(string, '.') < string + lenstr - lenpat)
/*
* If there is a dot left of where the pattern started to match, we
* don't match (rule 3)
*/
return 0;
/* String ended with pattern, and didn't have a dot before, so we match */
return 1;
}
/*
* Verify that common name resolves to peer.
*/
static bool
verify_peer_name_matches_certificate(PGconn *conn)
{
char *peer_cn;
int r;
int len;
bool result;
/*
* If told not to verify the peer name, don't do it. Return true
* indicating that the verification was successful.
*/
if (strcmp(conn->sslmode, "verify-full") != 0)
return true;
/*
* Extract the common name from the certificate.
*
* XXX: Should support alternate names here
*/
/* First find out the name's length and allocate a buffer for it. */
len = X509_NAME_get_text_by_NID(X509_get_subject_name(conn->peer),
NID_commonName, NULL, 0);
if (len == -1)
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not get server common name from server certificate\n"));
return false;
}
peer_cn = malloc(len + 1);
if (peer_cn == NULL)
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("out of memory\n"));
return false;
}
r = X509_NAME_get_text_by_NID(X509_get_subject_name(conn->peer),
NID_commonName, peer_cn, len + 1);
if (r != len)
{
/* Got different length than on the first call. Shouldn't happen. */
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not get server common name from server certificate\n"));
free(peer_cn);
return false;
}
peer_cn[len] = '\0';
/*
* Reject embedded NULLs in certificate common name to prevent attacks
* like CVE-2009-4034.
*/
if (len != strlen(peer_cn))
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("SSL certificate's common name contains embedded null\n"));
free(peer_cn);
return false;
}
/*
* We got the peer's common name. Now compare it against the originally
* given hostname.
*/
if (!(conn->pghost && conn->pghost[0] != '\0'))
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("host name must be specified for a verified SSL connection\n"));
result = false;
}
else
{
if (pg_strcasecmp(peer_cn, conn->pghost) == 0)
/* Exact name match */
result = true;
else if (wildcard_certificate_match(peer_cn, conn->pghost))
/* Matched wildcard certificate */
result = true;
else
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("server common name \"%s\" does not match host name \"%s\"\n"),
peer_cn, conn->pghost);
result = false;
}
}
free(peer_cn);
return result;
}
#ifdef ENABLE_THREAD_SAFETY
/*
* Callback functions for OpenSSL internal locking
*/
static unsigned long
pq_threadidcallback(void)
{
/*
* This is not standards-compliant. pthread_self() returns pthread_t, and
* shouldn't be cast to unsigned long, but CRYPTO_set_id_callback requires
* it, so we have to do it.
*/
return (unsigned long) pthread_self();
}
static pthread_mutex_t *pq_lockarray;
static void
pq_lockingcallback(int mode, int n, const char *file, int line)
{
if (mode & CRYPTO_LOCK)
{
if (pthread_mutex_lock(&pq_lockarray[n]))
PGTHREAD_ERROR("failed to lock mutex");
}
else
{
if (pthread_mutex_unlock(&pq_lockarray[n]))
PGTHREAD_ERROR("failed to unlock mutex");
}
}
#endif /* ENABLE_THREAD_SAFETY */
/*
* Initialize SSL system, in particular creating the SSL_context object
* that will be shared by all SSL-using connections in this process.
*
* In threadsafe mode, this includes setting up libcrypto callback functions
* to do thread locking.
*
* If the caller has told us (through PQinitOpenSSL) that he's taking care
* of libcrypto, we expect that callbacks are already set, and won't try to
* override it.
*
* The conn parameter is only used to be able to pass back an error
* message - no connection-local setup is made here.
*
* Returns 0 if OK, -1 on failure (with a message in conn->errorMessage).
*/
int
pgtls_init(PGconn *conn)
{
#ifdef ENABLE_THREAD_SAFETY
#ifdef WIN32
/* Also see similar code in fe-connect.c, default_threadlock() */
if (ssl_config_mutex == NULL)
{
while (InterlockedExchange(&win32_ssl_create_mutex, 1) == 1)
/* loop, another thread own the lock */ ;
if (ssl_config_mutex == NULL)
{
if (pthread_mutex_init(&ssl_config_mutex, NULL))
return -1;
}
InterlockedExchange(&win32_ssl_create_mutex, 0);
}
#endif
if (pthread_mutex_lock(&ssl_config_mutex))
return -1;
if (pq_init_crypto_lib)
{
/*
* If necessary, set up an array to hold locks for libcrypto.
* libcrypto will tell us how big to make this array.
*/
if (pq_lockarray == NULL)
{
int i;
pq_lockarray = malloc(sizeof(pthread_mutex_t) * CRYPTO_num_locks());
if (!pq_lockarray)
{
pthread_mutex_unlock(&ssl_config_mutex);
return -1;
}
for (i = 0; i < CRYPTO_num_locks(); i++)
{
if (pthread_mutex_init(&pq_lockarray[i], NULL))
{
free(pq_lockarray);
pq_lockarray = NULL;
pthread_mutex_unlock(&ssl_config_mutex);
return -1;
}
}
}
if (ssl_open_connections++ == 0)
{
/* These are only required for threaded libcrypto applications */
CRYPTO_set_id_callback(pq_threadidcallback);
CRYPTO_set_locking_callback(pq_lockingcallback);
}
}
#endif /* ENABLE_THREAD_SAFETY */
if (!SSL_context)
{
if (pq_init_ssl_lib)
{
#if SSLEAY_VERSION_NUMBER >= 0x00907000L
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)
{
char *err = SSLerrmessage();
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not create SSL context: %s\n"),
err);
SSLerrfree(err);
#ifdef ENABLE_THREAD_SAFETY
pthread_mutex_unlock(&ssl_config_mutex);
#endif
return -1;
}
/* Disable old protocol versions */
SSL_CTX_set_options(SSL_context, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
/*
* 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);
}
#ifdef ENABLE_THREAD_SAFETY
pthread_mutex_unlock(&ssl_config_mutex);
#endif
return 0;
}
/*
* This function is needed because if the libpq library is unloaded
* from the application, the callback functions will no longer exist when
* libcrypto is used by other parts of the system. For this reason,
* we unregister the callback functions when the last libpq
* connection is closed. (The same would apply for OpenSSL callbacks
* if we had any.)
*
* Callbacks are only set when we're compiled in threadsafe mode, so
* we only need to remove them in this case.
*/
static void
destroy_ssl_system(void)
{
#ifdef ENABLE_THREAD_SAFETY
/* Mutex is created in initialize_ssl_system() */
if (pthread_mutex_lock(&ssl_config_mutex))
return;
if (pq_init_crypto_lib && ssl_open_connections > 0)
--ssl_open_connections;
if (pq_init_crypto_lib && ssl_open_connections == 0)
{
/* No connections left, unregister libcrypto callbacks */
CRYPTO_set_locking_callback(NULL);
CRYPTO_set_id_callback(NULL);
/*
* We don't free the lock array or the SSL_context. If we get another
* connection in this process, we will just re-use them with the
* existing mutexes.
*
* This means we leak a little memory on repeated load/unload of the
* library.
*/
}
pthread_mutex_unlock(&ssl_config_mutex);
#endif
}
/*
* Initialize (potentially) per-connection SSL data, namely the
* client certificate, private key, and trusted CA certs.
*
* conn->ssl must already be created. It receives the connection's client
* certificate and private key. Note however that certificates also get
* loaded into the SSL_context object, and are therefore accessible to all
* connections in this process. This should be OK as long as there aren't
* any hash collisions among the certs.
*
* Returns 0 if OK, -1 on failure (with a message in conn->errorMessage).
*/
static int
initialize_SSL(PGconn *conn)
{
struct stat buf;
char homedir[MAXPGPATH];
char fnbuf[MAXPGPATH];
char sebuf[256];
bool have_homedir;
bool have_cert;
EVP_PKEY *pkey = NULL;
/*
* We'll need the home directory if any of the relevant parameters are
* defaulted. If pqGetHomeDirectory fails, act as though none of the
* files could be found.
*/
if (!(conn->sslcert && strlen(conn->sslcert) > 0) ||
!(conn->sslkey && strlen(conn->sslkey) > 0) ||
!(conn->sslrootcert && strlen(conn->sslrootcert) > 0) ||
!(conn->sslcrl && strlen(conn->sslcrl) > 0))
have_homedir = pqGetHomeDirectory(homedir, sizeof(homedir));
else /* won't need it */
have_homedir = false;
/* Read the client certificate file */
if (conn->sslcert && strlen(conn->sslcert) > 0)
strncpy(fnbuf, conn->sslcert, sizeof(fnbuf));
else if (have_homedir)
snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_CERT_FILE);
else
fnbuf[0] = '\0';
if (fnbuf[0] == '\0')
{
/* no home directory, proceed without a client cert */
have_cert = false;
}
else if (stat(fnbuf, &buf) != 0)
{
/*
* If file is not present, just go on without a client cert; server
* might or might not accept the connection. Any other error,
* however, is grounds for complaint.
*/
if (errno != ENOENT && errno != ENOTDIR)
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not open certificate file \"%s\": %s\n"),
fnbuf, pqStrerror(errno, sebuf, sizeof(sebuf)));
return -1;
}
have_cert = false;
}
else
{
/*
* Cert file exists, so load it. Since OpenSSL doesn't provide the
* equivalent of "SSL_use_certificate_chain_file", we actually have to
* load the file twice. The first call loads any extra certs after
* the first one into chain-cert storage associated with the
* SSL_context. The second call loads the first cert (only) into the
* SSL object, where it will be correctly paired with the private key
* we load below. We do it this way so that each connection
* understands which subject cert to present, in case different
* sslcert settings are used for different connections in the same
* process.
*
* NOTE: This function may also modify our SSL_context and therefore
* we have to lock around this call and any places where we use the
* SSL_context struct.
*/
#ifdef ENABLE_THREAD_SAFETY
int rc;
if ((rc = pthread_mutex_lock(&ssl_config_mutex)))
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not acquire mutex: %s\n"), strerror(rc));
return -1;
}
#endif
if (SSL_CTX_use_certificate_chain_file(SSL_context, fnbuf) != 1)
{
char *err = SSLerrmessage();
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not read certificate file \"%s\": %s\n"),
fnbuf, err);
SSLerrfree(err);
#ifdef ENABLE_THREAD_SAFETY
pthread_mutex_unlock(&ssl_config_mutex);
#endif
return -1;
}
if (SSL_use_certificate_file(conn->ssl, fnbuf, SSL_FILETYPE_PEM) != 1)
{
char *err = SSLerrmessage();
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not read certificate file \"%s\": %s\n"),
fnbuf, err);
SSLerrfree(err);
#ifdef ENABLE_THREAD_SAFETY
pthread_mutex_unlock(&ssl_config_mutex);
#endif
return -1;
}
/* need to load the associated private key, too */
have_cert = true;
#ifdef ENABLE_THREAD_SAFETY
pthread_mutex_unlock(&ssl_config_mutex);
#endif
}
/*
* Read the SSL key. If a key is specified, treat it as an engine:key
* combination if there is colon present - we don't support files with
* colon in the name. The exception is if the second character is a colon,
* in which case it can be a Windows filename with drive specification.
*/
if (have_cert && conn->sslkey && strlen(conn->sslkey) > 0)
{
#ifdef USE_SSL_ENGINE
if (strchr(conn->sslkey, ':')
#ifdef WIN32
&& conn->sslkey[1] != ':'
#endif
)
{
/* Colon, but not in second character, treat as engine:key */
char *engine_str = strdup(conn->sslkey);
char *engine_colon;
if (engine_str == NULL)
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("out of memory\n"));
return -1;
}
/* cannot return NULL because we already checked before strdup */
engine_colon = strchr(engine_str, ':');
*engine_colon = '\0'; /* engine_str now has engine name */
engine_colon++; /* engine_colon now has key name */
conn->engine = ENGINE_by_id(engine_str);
if (conn->engine == NULL)
{
char *err = SSLerrmessage();
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not load SSL engine \"%s\": %s\n"),
engine_str, err);
SSLerrfree(err);
free(engine_str);
return -1;
}
if (ENGINE_init(conn->engine) == 0)
{
char *err = SSLerrmessage();
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not initialize SSL engine \"%s\": %s\n"),
engine_str, err);
SSLerrfree(err);
ENGINE_free(conn->engine);
conn->engine = NULL;
free(engine_str);
return -1;
}
pkey = ENGINE_load_private_key(conn->engine, engine_colon,
NULL, NULL);
if (pkey == NULL)
{
char *err = SSLerrmessage();
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not read private SSL key \"%s\" from engine \"%s\": %s\n"),
engine_colon, engine_str, err);
SSLerrfree(err);
ENGINE_finish(conn->engine);
ENGINE_free(conn->engine);
conn->engine = NULL;
free(engine_str);
return -1;
}
if (SSL_use_PrivateKey(conn->ssl, pkey) != 1)
{
char *err = SSLerrmessage();
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not load private SSL key \"%s\" from engine \"%s\": %s\n"),
engine_colon, engine_str, err);
SSLerrfree(err);
ENGINE_finish(conn->engine);
ENGINE_free(conn->engine);
conn->engine = NULL;
free(engine_str);
return -1;
}
free(engine_str);
fnbuf[0] = '\0'; /* indicate we're not going to load from a
* file */
}
else
#endif /* USE_SSL_ENGINE */
{
/* PGSSLKEY is not an engine, treat it as a filename */
strncpy(fnbuf, conn->sslkey, sizeof(fnbuf));
}
}
else if (have_homedir)
{
/* No PGSSLKEY specified, load default file */
snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_KEY_FILE);
}
else
fnbuf[0] = '\0';
if (have_cert && fnbuf[0] != '\0')
{
/* read the client key from file */
if (stat(fnbuf, &buf) != 0)
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("certificate present, but not private key file \"%s\"\n"),
fnbuf);
return -1;
}
#ifndef WIN32
if (!S_ISREG(buf.st_mode) || buf.st_mode & (S_IRWXG | S_IRWXO))
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("private key file \"%s\" has group or world access; permissions should be u=rw (0600) or less\n"),
fnbuf);
return -1;
}
#endif
if (SSL_use_PrivateKey_file(conn->ssl, fnbuf, SSL_FILETYPE_PEM) != 1)
{
char *err = SSLerrmessage();
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not load private key file \"%s\": %s\n"),
fnbuf, err);
SSLerrfree(err);
return -1;
}
}
/* verify that the cert and key go together */
if (have_cert &&
SSL_check_private_key(conn->ssl) != 1)
{
char *err = SSLerrmessage();
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("certificate does not match private key file \"%s\": %s\n"),
fnbuf, err);
SSLerrfree(err);
return -1;
}
/*
* If the root cert file exists, load it so we can perform certificate
* verification. If sslmode is "verify-full" we will also do further
* verification after the connection has been completed.
*/
if (conn->sslrootcert && strlen(conn->sslrootcert) > 0)
strncpy(fnbuf, conn->sslrootcert, sizeof(fnbuf));
else if (have_homedir)
snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, ROOT_CERT_FILE);
else
fnbuf[0] = '\0';
if (fnbuf[0] != '\0' &&
stat(fnbuf, &buf) == 0)
{
X509_STORE *cvstore;
#ifdef ENABLE_THREAD_SAFETY
int rc;
if ((rc = pthread_mutex_lock(&ssl_config_mutex)))
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not acquire mutex: %s\n"), strerror(rc));
return -1;
}
#endif
if (SSL_CTX_load_verify_locations(SSL_context, fnbuf, NULL) != 1)
{
char *err = SSLerrmessage();
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not read root certificate file \"%s\": %s\n"),
fnbuf, err);
SSLerrfree(err);
#ifdef ENABLE_THREAD_SAFETY
pthread_mutex_unlock(&ssl_config_mutex);
#endif
return -1;
}
if ((cvstore = SSL_CTX_get_cert_store(SSL_context)) != NULL)
{
if (conn->sslcrl && strlen(conn->sslcrl) > 0)
strncpy(fnbuf, conn->sslcrl, sizeof(fnbuf));
else if (have_homedir)
snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, ROOT_CRL_FILE);
else
fnbuf[0] = '\0';
/* Set the flags to check against the complete CRL chain */
if (fnbuf[0] != '\0' &&
X509_STORE_load_locations(cvstore, fnbuf, 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
char *err = SSLerrmessage();
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("SSL library does not support CRL certificates (file \"%s\")\n"),
fnbuf);
SSLerrfree(err);
#ifdef ENABLE_THREAD_SAFETY
pthread_mutex_unlock(&ssl_config_mutex);
#endif
return -1;
#endif
}
/* if not found, silently ignore; we do not require CRL */
}
#ifdef ENABLE_THREAD_SAFETY
pthread_mutex_unlock(&ssl_config_mutex);
#endif
SSL_set_verify(conn->ssl, SSL_VERIFY_PEER, verify_cb);
}
else
{
/*
* stat() failed; assume root file doesn't exist. If sslmode is
* verify-ca or verify-full, this is an error. Otherwise, continue
* without performing any server cert verification.
*/
if (conn->sslmode[0] == 'v') /* "verify-ca" or "verify-full" */
{
/*
* The only way to reach here with an empty filename is if
* pqGetHomeDirectory failed. That's a sufficiently unusual case
* that it seems worth having a specialized error message for it.
*/
if (fnbuf[0] == '\0')
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not get home directory to locate root certificate file\n"
"Either provide the file or change sslmode to disable server certificate verification.\n"));
else
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("root certificate file \"%s\" does not exist\n"
"Either provide the file or change sslmode to disable server certificate verification.\n"), fnbuf);
return -1;
}
}
/*
* If the OpenSSL version used supports it (from 1.0.0 on) and the user
* requested it, disable SSL compression.
*/
#ifdef SSL_OP_NO_COMPRESSION
if (conn->sslcompression && conn->sslcompression[0] == '0')
{
SSL_set_options(conn->ssl, SSL_OP_NO_COMPRESSION);
}
#endif
return 0;
}
/*
* Attempt to negotiate SSL connection.
*/
static PostgresPollingStatusType
open_client_SSL(PGconn *conn)
{
int r;
r = SSL_connect(conn->ssl);
if (r <= 0)
{
int err = SSL_get_error(conn->ssl, r);
switch (err)
{
case SSL_ERROR_WANT_READ:
return PGRES_POLLING_READING;
case SSL_ERROR_WANT_WRITE:
return PGRES_POLLING_WRITING;
case SSL_ERROR_SYSCALL:
{
char sebuf[256];
if (r == -1)
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("SSL SYSCALL error: %s\n"),
SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
else
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("SSL SYSCALL error: EOF detected\n"));
pgtls_close(conn);
return PGRES_POLLING_FAILED;
}
case SSL_ERROR_SSL:
{
char *err = SSLerrmessage();
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("SSL error: %s\n"),
err);
SSLerrfree(err);
pgtls_close(conn);
return PGRES_POLLING_FAILED;
}
default:
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("unrecognized SSL error code: %d\n"),
err);
pgtls_close(conn);
return PGRES_POLLING_FAILED;
}
}
/*
* We already checked the server certificate in initialize_SSL() using
* SSL_CTX_set_verify(), if root.crt exists.
*/
/* get server certificate */
conn->peer = SSL_get_peer_certificate(conn->ssl);
if (conn->peer == NULL)
{
char *err = SSLerrmessage();
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("certificate could not be obtained: %s\n"),
err);
SSLerrfree(err);
pgtls_close(conn);
return PGRES_POLLING_FAILED;
}
if (!verify_peer_name_matches_certificate(conn))
{
pgtls_close(conn);
return PGRES_POLLING_FAILED;
}
/* SSL handshake is complete */
return PGRES_POLLING_OK;
}
/*
* Close SSL connection.
*/
void
pgtls_close(PGconn *conn)
{
bool destroy_needed = false;
if (conn->ssl)
{
/*
* We can't destroy everything SSL-related here due to the possible
* later calls to OpenSSL routines which may need our thread
* callbacks, so set a flag here and check at the end.
*/
destroy_needed = true;
SSL_shutdown(conn->ssl);
SSL_free(conn->ssl);
conn->ssl = NULL;
conn->ssl_in_use = false;
}
if (conn->peer)
{
X509_free(conn->peer);
conn->peer = NULL;
}
#ifdef USE_SSL_ENGINE
if (conn->engine)
{
ENGINE_finish(conn->engine);
ENGINE_free(conn->engine);
conn->engine = NULL;
}
#endif
/*
* This will remove our SSL locking hooks, if this is the last SSL
* connection, which means we must wait to call it until after all SSL
* calls have been made, otherwise we can end up with a race condition and
* possible deadlocks.
*
* See comments above destroy_ssl_system().
*/
if (destroy_needed)
destroy_ssl_system();
}
/*
* 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 char ssl_nomem[] = "out of memory allocating error description";
#define SSL_ERR_LEN 128
static char *
SSLerrmessage(void)
{
unsigned long errcode;
const char *errreason;
char *errbuf;
errbuf = malloc(SSL_ERR_LEN);
if (!errbuf)
return ssl_nomem;
errcode = ERR_get_error();
if (errcode == 0)
{
snprintf(errbuf, SSL_ERR_LEN, libpq_gettext("no SSL error reported"));
return errbuf;
}
errreason = ERR_reason_error_string(errcode);
if (errreason != NULL)
{
strlcpy(errbuf, errreason, SSL_ERR_LEN);
return errbuf;
}
snprintf(errbuf, SSL_ERR_LEN, libpq_gettext("SSL error code %lu"), errcode);
return errbuf;
}
static void
SSLerrfree(char *buf)
{
if (buf != ssl_nomem)
free(buf);
}
/*
* Return pointer to OpenSSL object.
*/
void *
PQgetssl(PGconn *conn)
{
if (!conn)
return NULL;
return conn->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;
int save_errno;
res = pqsecure_raw_read((PGconn *) h->ptr, buf, size);
save_errno = errno;
BIO_clear_retry_flags(h);
if (res < 0)
{
switch (save_errno)
{
#ifdef EAGAIN
case EAGAIN:
#endif
#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
case EWOULDBLOCK:
#endif
case EINTR:
BIO_set_retry_read(h);
break;
default:
break;
}
}
errno = save_errno;
return res;
}
static int
my_sock_write(BIO *h, const char *buf, int size)
{
int res;
int save_errno;
res = pqsecure_raw_write((PGconn *) h->ptr, buf, size);
save_errno = errno;
BIO_clear_retry_flags(h);
if (res <= 0)
{
if (save_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(PGconn *conn, 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 = conn;
SSL_set_bio(conn->ssl, bio, bio);
BIO_set_fd(bio, fd, BIO_NOCLOSE);
ret = 1;
err:
return ret;
}
......@@ -55,64 +55,6 @@
#endif
#endif
#ifdef USE_SSL
#include <openssl/ssl.h>
#if (SSLEAY_VERSION_NUMBER >= 0x00907000L)
#include <openssl/conf.h>
#endif
#ifdef USE_SSL_ENGINE
#include <openssl/engine.h>
#endif
#ifndef WIN32
#define USER_CERT_FILE ".postgresql/postgresql.crt"
#define USER_KEY_FILE ".postgresql/postgresql.key"
#define ROOT_CERT_FILE ".postgresql/root.crt"
#define ROOT_CRL_FILE ".postgresql/root.crl"
#else
/* On Windows, the "home" directory is already PostgreSQL-specific */
#define USER_CERT_FILE "postgresql.crt"
#define USER_KEY_FILE "postgresql.key"
#define ROOT_CERT_FILE "root.crt"
#define ROOT_CRL_FILE "root.crl"
#endif
static bool verify_peer_name_matches_certificate(PGconn *);
static int verify_cb(int ok, X509_STORE_CTX *ctx);
static int init_ssl_system(PGconn *conn);
static void destroy_ssl_system(void);
static int initialize_SSL(PGconn *conn);
static void destroySSL(void);
static PostgresPollingStatusType open_client_SSL(PGconn *);
static void close_SSL(PGconn *);
static char *SSLerrmessage(void);
static void SSLerrfree(char *buf);
static bool pq_init_ssl_lib = true;
static bool pq_init_crypto_lib = true;
/*
* SSL_context is currently shared between threads and therefore we need to be
* careful to lock around any usage of it when providing thread safety.
* ssl_config_mutex is the mutex that we use to protect it.
*/
static SSL_CTX *SSL_context = NULL;
#ifdef ENABLE_THREAD_SAFETY
static long ssl_open_connections = 0;
#ifndef WIN32
static pthread_mutex_t ssl_config_mutex = PTHREAD_MUTEX_INITIALIZER;
#else
static pthread_mutex_t ssl_config_mutex = NULL;
static long win32_ssl_create_mutex = 0;
#endif
#endif /* ENABLE_THREAD_SAFETY */
#endif /* SSL */
/*
* Macros to handle disabling and then restoring the state of SIGPIPE handling.
* On Windows, these are all no-ops since there's no SIGPIPEs.
......@@ -194,7 +136,9 @@ struct sigpipe_info
void
PQinitSSL(int do_init)
{
PQinitOpenSSL(do_init, do_init);
#ifdef USE_SSL
pgtls_init_library(do_init, do_init);
#endif
}
/*
......@@ -205,18 +149,7 @@ void
PQinitOpenSSL(int do_ssl, int do_crypto)
{
#ifdef USE_SSL
#ifdef ENABLE_THREAD_SAFETY
/*
* Disallow changing the flags while we have open connections, else we'd
* get completely confused.
*/
if (ssl_open_connections != 0)
return;
#endif
pq_init_ssl_lib = do_ssl;
pq_init_crypto_lib = do_crypto;
pgtls_init_library(do_ssl, do_crypto);
#endif
}
......@@ -229,23 +162,12 @@ pqsecure_initialize(PGconn *conn)
int r = 0;
#ifdef USE_SSL
r = init_ssl_system(conn);
r = pgtls_init(conn);
#endif
return r;
}
/*
* Destroy global context
*/
void
pqsecure_destroy(void)
{
#ifdef USE_SSL
destroySSL();
#endif
}
/*
* Begin or continue negotiating a secure session.
*/
......@@ -253,59 +175,7 @@ PostgresPollingStatusType
pqsecure_open_client(PGconn *conn)
{
#ifdef USE_SSL
/* First time through? */
if (conn->ssl == NULL)
{
#ifdef ENABLE_THREAD_SAFETY
int rc;
#endif
/* We cannot use MSG_NOSIGNAL to block SIGPIPE when using SSL */
conn->sigpipe_flag = false;
#ifdef ENABLE_THREAD_SAFETY
if ((rc = pthread_mutex_lock(&ssl_config_mutex)))
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not acquire mutex: %s\n"), strerror(rc));
return PGRES_POLLING_FAILED;
}
#endif
/* Create a connection-specific SSL object */
if (!(conn->ssl = SSL_new(SSL_context)) ||
!SSL_set_app_data(conn->ssl, conn) ||
!SSL_set_fd(conn->ssl, conn->sock))
{
char *err = SSLerrmessage();
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not establish SSL connection: %s\n"),
err);
SSLerrfree(err);
#ifdef ENABLE_THREAD_SAFETY
pthread_mutex_unlock(&ssl_config_mutex);
#endif
close_SSL(conn);
return PGRES_POLLING_FAILED;
}
#ifdef ENABLE_THREAD_SAFETY
pthread_mutex_unlock(&ssl_config_mutex);
#endif
/*
* Load client certificate, private key, and trusted CA certs.
*/
if (initialize_SSL(conn) != 0)
{
/* initialize_SSL already put a message in conn->errorMessage */
close_SSL(conn);
return PGRES_POLLING_FAILED;
}
}
/* Begin or continue the actual handshake */
return open_client_SSL(conn);
return pgtls_open_client(conn);
#else
/* shouldn't get here */
return PGRES_POLLING_FAILED;
......@@ -319,8 +189,8 @@ void
pqsecure_close(PGconn *conn)
{
#ifdef USE_SSL
if (conn->ssl)
close_SSL(conn);
if (conn->ssl_in_use)
pgtls_close(conn);
#endif
}
......@@ -335,113 +205,28 @@ ssize_t
pqsecure_read(PGconn *conn, void *ptr, size_t len)
{
ssize_t n;
int result_errno = 0;
char sebuf[256];
#ifdef USE_SSL
if (conn->ssl)
{
int err;
DECLARE_SIGPIPE_INFO(spinfo);
/* SSL_read can write to the socket, so we need to disable SIGPIPE */
DISABLE_SIGPIPE(conn, spinfo, return -1);
rloop:
SOCK_ERRNO_SET(0);
n = SSL_read(conn->ssl, ptr, len);
err = SSL_get_error(conn->ssl, n);
switch (err)
{
case SSL_ERROR_NONE:
if (n < 0)
{
/* Not supposed to happen, so we don't translate the msg */
printfPQExpBuffer(&conn->errorMessage,
"SSL_read failed but did not provide error information\n");
/* assume the connection is broken */
result_errno = ECONNRESET;
}
break;
case SSL_ERROR_WANT_READ:
n = 0;
break;
case SSL_ERROR_WANT_WRITE:
/*
* Returning 0 here would cause caller to wait for read-ready,
* which is not correct since what SSL wants is wait for
* write-ready. The former could get us stuck in an infinite
* wait, so don't risk it; busy-loop instead.
*/
goto rloop;
case SSL_ERROR_SYSCALL:
if (n < 0)
if (conn->ssl_in_use)
{
result_errno = SOCK_ERRNO;
REMEMBER_EPIPE(spinfo, result_errno == EPIPE);
if (result_errno == EPIPE ||
result_errno == ECONNRESET)
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext(
"server closed the connection unexpectedly\n"
"\tThis probably means the server terminated abnormally\n"
"\tbefore or while processing the request.\n"));
else
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("SSL SYSCALL error: %s\n"),
SOCK_STRERROR(result_errno,
sebuf, sizeof(sebuf)));
n = pgtls_read(conn, ptr, len);
}
else
#endif
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("SSL SYSCALL error: EOF detected\n"));
/* assume the connection is broken */
result_errno = ECONNRESET;
n = -1;
n = pqsecure_raw_read(conn, ptr, len);
}
break;
case SSL_ERROR_SSL:
{
char *errm = SSLerrmessage();
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("SSL error: %s\n"), errm);
SSLerrfree(errm);
/* assume the connection is broken */
result_errno = ECONNRESET;
n = -1;
break;
}
case SSL_ERROR_ZERO_RETURN:
return n;
}
/*
* Per OpenSSL documentation, this error code is only returned
* for a clean connection closure, so we should not report it
* as a server crash.
*/
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("SSL connection has been closed unexpectedly\n"));
result_errno = ECONNRESET;
n = -1;
break;
default:
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("unrecognized SSL error code: %d\n"),
err);
/* assume the connection is broken */
result_errno = ECONNRESET;
n = -1;
break;
}
ssize_t
pqsecure_raw_read(PGconn *conn, void *ptr, size_t len)
{
ssize_t n;
int result_errno = 0;
char sebuf[256];
RESTORE_SIGPIPE(conn, spinfo);
}
else
#endif /* USE_SSL */
{
n = recv(conn->sock, ptr, len, 0);
if (n < 0)
......@@ -479,7 +264,6 @@ rloop:
break;
}
}
}
/* ensure we return the intended errno to caller */
SOCK_ERRNO_SET(result_errno);
......@@ -498,110 +282,30 @@ ssize_t
pqsecure_write(PGconn *conn, const void *ptr, size_t len)
{
ssize_t n;
int result_errno = 0;
char sebuf[256];
DECLARE_SIGPIPE_INFO(spinfo);
#ifdef USE_SSL
if (conn->ssl)
{
int err;
DISABLE_SIGPIPE(conn, spinfo, return -1);
SOCK_ERRNO_SET(0);
n = SSL_write(conn->ssl, ptr, len);
err = SSL_get_error(conn->ssl, n);
switch (err)
{
case SSL_ERROR_NONE:
if (n < 0)
{
/* Not supposed to happen, so we don't translate the msg */
printfPQExpBuffer(&conn->errorMessage,
"SSL_write failed but did not provide error information\n");
/* assume the connection is broken */
result_errno = ECONNRESET;
}
break;
case SSL_ERROR_WANT_READ:
/*
* Returning 0 here causes caller to wait for write-ready,
* which is not really the right thing, but it's the best we
* can do.
*/
n = 0;
break;
case SSL_ERROR_WANT_WRITE:
n = 0;
break;
case SSL_ERROR_SYSCALL:
if (n < 0)
if (conn->ssl_in_use)
{
result_errno = SOCK_ERRNO;
REMEMBER_EPIPE(spinfo, result_errno == EPIPE);
if (result_errno == EPIPE ||
result_errno == ECONNRESET)
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext(
"server closed the connection unexpectedly\n"
"\tThis probably means the server terminated abnormally\n"
"\tbefore or while processing the request.\n"));
else
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("SSL SYSCALL error: %s\n"),
SOCK_STRERROR(result_errno,
sebuf, sizeof(sebuf)));
n = pgtls_write(conn, ptr, len);
}
else
#endif
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("SSL SYSCALL error: EOF detected\n"));
/* assume the connection is broken */
result_errno = ECONNRESET;
n = -1;
n = pqsecure_raw_write(conn, ptr, len);
}
break;
case SSL_ERROR_SSL:
{
char *errm = SSLerrmessage();
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("SSL error: %s\n"), errm);
SSLerrfree(errm);
/* assume the connection is broken */
result_errno = ECONNRESET;
n = -1;
break;
}
case SSL_ERROR_ZERO_RETURN:
return n;
}
/*
* Per OpenSSL documentation, this error code is only returned
* for a clean connection closure, so we should not report it
* as a server crash.
*/
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("SSL connection has been closed unexpectedly\n"));
result_errno = ECONNRESET;
n = -1;
break;
default:
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("unrecognized SSL error code: %d\n"),
err);
/* assume the connection is broken */
result_errno = ECONNRESET;
n = -1;
break;
}
}
else
#endif /* USE_SSL */
{
ssize_t
pqsecure_raw_write(PGconn *conn, const void *ptr, size_t len)
{
ssize_t n;
int flags = 0;
int result_errno = 0;
char sebuf[256];
DECLARE_SIGPIPE_INFO(spinfo);
#ifdef MSG_NOSIGNAL
if (conn->sigpipe_flag)
......@@ -668,7 +372,6 @@ retry_masked:
break;
}
}
}
RESTORE_SIGPIPE(conn, spinfo);
......@@ -678,981 +381,7 @@ retry_masked:
return n;
}
/* ------------------------------------------------------------ */
/* SSL specific code */
/* ------------------------------------------------------------ */
#ifdef USE_SSL
/*
* Certificate verification callback
*
* This callback allows us to log intermediate problems during
* verification, but there doesn't seem to be a clean way to get
* our PGconn * structure. So we can't log anything!
*
* 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;
}
/*
* Check if a wildcard certificate matches the server hostname.
*
* The rule for this is:
* 1. We only match the '*' character as wildcard
* 2. We match only wildcards at the start of the string
* 3. The '*' character does *not* match '.', meaning that we match only
* a single pathname component.
* 4. We don't support more than one '*' in a single pattern.
*
* This is roughly in line with RFC2818, but contrary to what most browsers
* appear to be implementing (point 3 being the difference)
*
* Matching is always case-insensitive, since DNS is case insensitive.
*/
static int
wildcard_certificate_match(const char *pattern, const char *string)
{
int lenpat = strlen(pattern);
int lenstr = strlen(string);
/* If we don't start with a wildcard, it's not a match (rule 1 & 2) */
if (lenpat < 3 ||
pattern[0] != '*' ||
pattern[1] != '.')
return 0;
if (lenpat > lenstr)
/* If pattern is longer than the string, we can never match */
return 0;
if (pg_strcasecmp(pattern + 1, string + lenstr - lenpat + 1) != 0)
/*
* If string does not end in pattern (minus the wildcard), we don't
* match
*/
return 0;
if (strchr(string, '.') < string + lenstr - lenpat)
/*
* If there is a dot left of where the pattern started to match, we
* don't match (rule 3)
*/
return 0;
/* String ended with pattern, and didn't have a dot before, so we match */
return 1;
}
/*
* Verify that common name resolves to peer.
*/
static bool
verify_peer_name_matches_certificate(PGconn *conn)
{
char *peer_cn;
int r;
int len;
bool result;
/*
* If told not to verify the peer name, don't do it. Return true
* indicating that the verification was successful.
*/
if (strcmp(conn->sslmode, "verify-full") != 0)
return true;
/*
* Extract the common name from the certificate.
*
* XXX: Should support alternate names here
*/
/* First find out the name's length and allocate a buffer for it. */
len = X509_NAME_get_text_by_NID(X509_get_subject_name(conn->peer),
NID_commonName, NULL, 0);
if (len == -1)
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not get server common name from server certificate\n"));
return false;
}
peer_cn = malloc(len + 1);
if (peer_cn == NULL)
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("out of memory\n"));
return false;
}
r = X509_NAME_get_text_by_NID(X509_get_subject_name(conn->peer),
NID_commonName, peer_cn, len + 1);
if (r != len)
{
/* Got different length than on the first call. Shouldn't happen. */
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not get server common name from server certificate\n"));
free(peer_cn);
return false;
}
peer_cn[len] = '\0';
/*
* Reject embedded NULLs in certificate common name to prevent attacks
* like CVE-2009-4034.
*/
if (len != strlen(peer_cn))
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("SSL certificate's common name contains embedded null\n"));
free(peer_cn);
return false;
}
/*
* We got the peer's common name. Now compare it against the originally
* given hostname.
*/
if (!(conn->pghost && conn->pghost[0] != '\0'))
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("host name must be specified for a verified SSL connection\n"));
result = false;
}
else
{
if (pg_strcasecmp(peer_cn, conn->pghost) == 0)
/* Exact name match */
result = true;
else if (wildcard_certificate_match(peer_cn, conn->pghost))
/* Matched wildcard certificate */
result = true;
else
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("server common name \"%s\" does not match host name \"%s\"\n"),
peer_cn, conn->pghost);
result = false;
}
}
free(peer_cn);
return result;
}
#ifdef ENABLE_THREAD_SAFETY
/*
* Callback functions for OpenSSL internal locking
*/
static unsigned long
pq_threadidcallback(void)
{
/*
* This is not standards-compliant. pthread_self() returns pthread_t, and
* shouldn't be cast to unsigned long, but CRYPTO_set_id_callback requires
* it, so we have to do it.
*/
return (unsigned long) pthread_self();
}
static pthread_mutex_t *pq_lockarray;
static void
pq_lockingcallback(int mode, int n, const char *file, int line)
{
if (mode & CRYPTO_LOCK)
{
if (pthread_mutex_lock(&pq_lockarray[n]))
PGTHREAD_ERROR("failed to lock mutex");
}
else
{
if (pthread_mutex_unlock(&pq_lockarray[n]))
PGTHREAD_ERROR("failed to unlock mutex");
}
}
#endif /* ENABLE_THREAD_SAFETY */
/*
* Initialize SSL system, in particular creating the SSL_context object
* that will be shared by all SSL-using connections in this process.
*
* In threadsafe mode, this includes setting up libcrypto callback functions
* to do thread locking.
*
* If the caller has told us (through PQinitOpenSSL) that he's taking care
* of libcrypto, we expect that callbacks are already set, and won't try to
* override it.
*
* The conn parameter is only used to be able to pass back an error
* message - no connection-local setup is made here.
*
* Returns 0 if OK, -1 on failure (with a message in conn->errorMessage).
*/
static int
init_ssl_system(PGconn *conn)
{
#ifdef ENABLE_THREAD_SAFETY
#ifdef WIN32
/* Also see similar code in fe-connect.c, default_threadlock() */
if (ssl_config_mutex == NULL)
{
while (InterlockedExchange(&win32_ssl_create_mutex, 1) == 1)
/* loop, another thread own the lock */ ;
if (ssl_config_mutex == NULL)
{
if (pthread_mutex_init(&ssl_config_mutex, NULL))
return -1;
}
InterlockedExchange(&win32_ssl_create_mutex, 0);
}
#endif
if (pthread_mutex_lock(&ssl_config_mutex))
return -1;
if (pq_init_crypto_lib)
{
/*
* If necessary, set up an array to hold locks for libcrypto.
* libcrypto will tell us how big to make this array.
*/
if (pq_lockarray == NULL)
{
int i;
pq_lockarray = malloc(sizeof(pthread_mutex_t) * CRYPTO_num_locks());
if (!pq_lockarray)
{
pthread_mutex_unlock(&ssl_config_mutex);
return -1;
}
for (i = 0; i < CRYPTO_num_locks(); i++)
{
if (pthread_mutex_init(&pq_lockarray[i], NULL))
{
free(pq_lockarray);
pq_lockarray = NULL;
pthread_mutex_unlock(&ssl_config_mutex);
return -1;
}
}
}
if (ssl_open_connections++ == 0)
{
/* These are only required for threaded libcrypto applications */
CRYPTO_set_id_callback(pq_threadidcallback);
CRYPTO_set_locking_callback(pq_lockingcallback);
}
}
#endif /* ENABLE_THREAD_SAFETY */
if (!SSL_context)
{
if (pq_init_ssl_lib)
{
#if SSLEAY_VERSION_NUMBER >= 0x00907000L
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)
{
char *err = SSLerrmessage();
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not create SSL context: %s\n"),
err);
SSLerrfree(err);
#ifdef ENABLE_THREAD_SAFETY
pthread_mutex_unlock(&ssl_config_mutex);
#endif
return -1;
}
/* Disable old protocol versions */
SSL_CTX_set_options(SSL_context, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
/*
* 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);
}
#ifdef ENABLE_THREAD_SAFETY
pthread_mutex_unlock(&ssl_config_mutex);
#endif
return 0;
}
/*
* This function is needed because if the libpq library is unloaded
* from the application, the callback functions will no longer exist when
* libcrypto is used by other parts of the system. For this reason,
* we unregister the callback functions when the last libpq
* connection is closed. (The same would apply for OpenSSL callbacks
* if we had any.)
*
* Callbacks are only set when we're compiled in threadsafe mode, so
* we only need to remove them in this case.
*/
static void
destroy_ssl_system(void)
{
#ifdef ENABLE_THREAD_SAFETY
/* Mutex is created in initialize_ssl_system() */
if (pthread_mutex_lock(&ssl_config_mutex))
return;
if (pq_init_crypto_lib && ssl_open_connections > 0)
--ssl_open_connections;
if (pq_init_crypto_lib && ssl_open_connections == 0)
{
/* No connections left, unregister libcrypto callbacks */
CRYPTO_set_locking_callback(NULL);
CRYPTO_set_id_callback(NULL);
/*
* We don't free the lock array or the SSL_context. If we get another
* connection in this process, we will just re-use them with the
* existing mutexes.
*
* This means we leak a little memory on repeated load/unload of the
* library.
*/
}
pthread_mutex_unlock(&ssl_config_mutex);
#endif
}
/*
* Initialize (potentially) per-connection SSL data, namely the
* client certificate, private key, and trusted CA certs.
*
* conn->ssl must already be created. It receives the connection's client
* certificate and private key. Note however that certificates also get
* loaded into the SSL_context object, and are therefore accessible to all
* connections in this process. This should be OK as long as there aren't
* any hash collisions among the certs.
*
* Returns 0 if OK, -1 on failure (with a message in conn->errorMessage).
*/
static int
initialize_SSL(PGconn *conn)
{
struct stat buf;
char homedir[MAXPGPATH];
char fnbuf[MAXPGPATH];
char sebuf[256];
bool have_homedir;
bool have_cert;
EVP_PKEY *pkey = NULL;
/*
* We'll need the home directory if any of the relevant parameters are
* defaulted. If pqGetHomeDirectory fails, act as though none of the
* files could be found.
*/
if (!(conn->sslcert && strlen(conn->sslcert) > 0) ||
!(conn->sslkey && strlen(conn->sslkey) > 0) ||
!(conn->sslrootcert && strlen(conn->sslrootcert) > 0) ||
!(conn->sslcrl && strlen(conn->sslcrl) > 0))
have_homedir = pqGetHomeDirectory(homedir, sizeof(homedir));
else /* won't need it */
have_homedir = false;
/* Read the client certificate file */
if (conn->sslcert && strlen(conn->sslcert) > 0)
strncpy(fnbuf, conn->sslcert, sizeof(fnbuf));
else if (have_homedir)
snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_CERT_FILE);
else
fnbuf[0] = '\0';
if (fnbuf[0] == '\0')
{
/* no home directory, proceed without a client cert */
have_cert = false;
}
else if (stat(fnbuf, &buf) != 0)
{
/*
* If file is not present, just go on without a client cert; server
* might or might not accept the connection. Any other error,
* however, is grounds for complaint.
*/
if (errno != ENOENT && errno != ENOTDIR)
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not open certificate file \"%s\": %s\n"),
fnbuf, pqStrerror(errno, sebuf, sizeof(sebuf)));
return -1;
}
have_cert = false;
}
else
{
/*
* Cert file exists, so load it. Since OpenSSL doesn't provide the
* equivalent of "SSL_use_certificate_chain_file", we actually have to
* load the file twice. The first call loads any extra certs after
* the first one into chain-cert storage associated with the
* SSL_context. The second call loads the first cert (only) into the
* SSL object, where it will be correctly paired with the private key
* we load below. We do it this way so that each connection
* understands which subject cert to present, in case different
* sslcert settings are used for different connections in the same
* process.
*
* NOTE: This function may also modify our SSL_context and therefore
* we have to lock around this call and any places where we use the
* SSL_context struct.
*/
#ifdef ENABLE_THREAD_SAFETY
int rc;
if ((rc = pthread_mutex_lock(&ssl_config_mutex)))
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not acquire mutex: %s\n"), strerror(rc));
return -1;
}
#endif
if (SSL_CTX_use_certificate_chain_file(SSL_context, fnbuf) != 1)
{
char *err = SSLerrmessage();
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not read certificate file \"%s\": %s\n"),
fnbuf, err);
SSLerrfree(err);
#ifdef ENABLE_THREAD_SAFETY
pthread_mutex_unlock(&ssl_config_mutex);
#endif
return -1;
}
if (SSL_use_certificate_file(conn->ssl, fnbuf, SSL_FILETYPE_PEM) != 1)
{
char *err = SSLerrmessage();
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not read certificate file \"%s\": %s\n"),
fnbuf, err);
SSLerrfree(err);
#ifdef ENABLE_THREAD_SAFETY
pthread_mutex_unlock(&ssl_config_mutex);
#endif
return -1;
}
/* need to load the associated private key, too */
have_cert = true;
#ifdef ENABLE_THREAD_SAFETY
pthread_mutex_unlock(&ssl_config_mutex);
#endif
}
/*
* Read the SSL key. If a key is specified, treat it as an engine:key
* combination if there is colon present - we don't support files with
* colon in the name. The exception is if the second character is a colon,
* in which case it can be a Windows filename with drive specification.
*/
if (have_cert && conn->sslkey && strlen(conn->sslkey) > 0)
{
#ifdef USE_SSL_ENGINE
if (strchr(conn->sslkey, ':')
#ifdef WIN32
&& conn->sslkey[1] != ':'
#endif
)
{
/* Colon, but not in second character, treat as engine:key */
char *engine_str = strdup(conn->sslkey);
char *engine_colon;
if (engine_str == NULL)
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("out of memory\n"));
return -1;
}
/* cannot return NULL because we already checked before strdup */
engine_colon = strchr(engine_str, ':');
*engine_colon = '\0'; /* engine_str now has engine name */
engine_colon++; /* engine_colon now has key name */
conn->engine = ENGINE_by_id(engine_str);
if (conn->engine == NULL)
{
char *err = SSLerrmessage();
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not load SSL engine \"%s\": %s\n"),
engine_str, err);
SSLerrfree(err);
free(engine_str);
return -1;
}
if (ENGINE_init(conn->engine) == 0)
{
char *err = SSLerrmessage();
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not initialize SSL engine \"%s\": %s\n"),
engine_str, err);
SSLerrfree(err);
ENGINE_free(conn->engine);
conn->engine = NULL;
free(engine_str);
return -1;
}
pkey = ENGINE_load_private_key(conn->engine, engine_colon,
NULL, NULL);
if (pkey == NULL)
{
char *err = SSLerrmessage();
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not read private SSL key \"%s\" from engine \"%s\": %s\n"),
engine_colon, engine_str, err);
SSLerrfree(err);
ENGINE_finish(conn->engine);
ENGINE_free(conn->engine);
conn->engine = NULL;
free(engine_str);
return -1;
}
if (SSL_use_PrivateKey(conn->ssl, pkey) != 1)
{
char *err = SSLerrmessage();
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not load private SSL key \"%s\" from engine \"%s\": %s\n"),
engine_colon, engine_str, err);
SSLerrfree(err);
ENGINE_finish(conn->engine);
ENGINE_free(conn->engine);
conn->engine = NULL;
free(engine_str);
return -1;
}
free(engine_str);
fnbuf[0] = '\0'; /* indicate we're not going to load from a
* file */
}
else
#endif /* USE_SSL_ENGINE */
{
/* PGSSLKEY is not an engine, treat it as a filename */
strncpy(fnbuf, conn->sslkey, sizeof(fnbuf));
}
}
else if (have_homedir)
{
/* No PGSSLKEY specified, load default file */
snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_KEY_FILE);
}
else
fnbuf[0] = '\0';
if (have_cert && fnbuf[0] != '\0')
{
/* read the client key from file */
if (stat(fnbuf, &buf) != 0)
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("certificate present, but not private key file \"%s\"\n"),
fnbuf);
return -1;
}
#ifndef WIN32
if (!S_ISREG(buf.st_mode) || buf.st_mode & (S_IRWXG | S_IRWXO))
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("private key file \"%s\" has group or world access; permissions should be u=rw (0600) or less\n"),
fnbuf);
return -1;
}
#endif
if (SSL_use_PrivateKey_file(conn->ssl, fnbuf, SSL_FILETYPE_PEM) != 1)
{
char *err = SSLerrmessage();
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not load private key file \"%s\": %s\n"),
fnbuf, err);
SSLerrfree(err);
return -1;
}
}
/* verify that the cert and key go together */
if (have_cert &&
SSL_check_private_key(conn->ssl) != 1)
{
char *err = SSLerrmessage();
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("certificate does not match private key file \"%s\": %s\n"),
fnbuf, err);
SSLerrfree(err);
return -1;
}
/*
* If the root cert file exists, load it so we can perform certificate
* verification. If sslmode is "verify-full" we will also do further
* verification after the connection has been completed.
*/
if (conn->sslrootcert && strlen(conn->sslrootcert) > 0)
strncpy(fnbuf, conn->sslrootcert, sizeof(fnbuf));
else if (have_homedir)
snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, ROOT_CERT_FILE);
else
fnbuf[0] = '\0';
if (fnbuf[0] != '\0' &&
stat(fnbuf, &buf) == 0)
{
X509_STORE *cvstore;
#ifdef ENABLE_THREAD_SAFETY
int rc;
if ((rc = pthread_mutex_lock(&ssl_config_mutex)))
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not acquire mutex: %s\n"), strerror(rc));
return -1;
}
#endif
if (SSL_CTX_load_verify_locations(SSL_context, fnbuf, NULL) != 1)
{
char *err = SSLerrmessage();
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not read root certificate file \"%s\": %s\n"),
fnbuf, err);
SSLerrfree(err);
#ifdef ENABLE_THREAD_SAFETY
pthread_mutex_unlock(&ssl_config_mutex);
#endif
return -1;
}
if ((cvstore = SSL_CTX_get_cert_store(SSL_context)) != NULL)
{
if (conn->sslcrl && strlen(conn->sslcrl) > 0)
strncpy(fnbuf, conn->sslcrl, sizeof(fnbuf));
else if (have_homedir)
snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, ROOT_CRL_FILE);
else
fnbuf[0] = '\0';
/* Set the flags to check against the complete CRL chain */
if (fnbuf[0] != '\0' &&
X509_STORE_load_locations(cvstore, fnbuf, 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
char *err = SSLerrmessage();
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("SSL library does not support CRL certificates (file \"%s\")\n"),
fnbuf);
SSLerrfree(err);
#ifdef ENABLE_THREAD_SAFETY
pthread_mutex_unlock(&ssl_config_mutex);
#endif
return -1;
#endif
}
/* if not found, silently ignore; we do not require CRL */
}
#ifdef ENABLE_THREAD_SAFETY
pthread_mutex_unlock(&ssl_config_mutex);
#endif
SSL_set_verify(conn->ssl, SSL_VERIFY_PEER, verify_cb);
}
else
{
/*
* stat() failed; assume root file doesn't exist. If sslmode is
* verify-ca or verify-full, this is an error. Otherwise, continue
* without performing any server cert verification.
*/
if (conn->sslmode[0] == 'v') /* "verify-ca" or "verify-full" */
{
/*
* The only way to reach here with an empty filename is if
* pqGetHomeDirectory failed. That's a sufficiently unusual case
* that it seems worth having a specialized error message for it.
*/
if (fnbuf[0] == '\0')
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not get home directory to locate root certificate file\n"
"Either provide the file or change sslmode to disable server certificate verification.\n"));
else
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("root certificate file \"%s\" does not exist\n"
"Either provide the file or change sslmode to disable server certificate verification.\n"), fnbuf);
return -1;
}
}
/*
* If the OpenSSL version used supports it (from 1.0.0 on) and the user
* requested it, disable SSL compression.
*/
#ifdef SSL_OP_NO_COMPRESSION
if (conn->sslcompression && conn->sslcompression[0] == '0')
{
SSL_set_options(conn->ssl, SSL_OP_NO_COMPRESSION);
}
#endif
return 0;
}
static void
destroySSL(void)
{
destroy_ssl_system();
}
/*
* Attempt to negotiate SSL connection.
*/
static PostgresPollingStatusType
open_client_SSL(PGconn *conn)
{
int r;
r = SSL_connect(conn->ssl);
if (r <= 0)
{
int err = SSL_get_error(conn->ssl, r);
switch (err)
{
case SSL_ERROR_WANT_READ:
return PGRES_POLLING_READING;
case SSL_ERROR_WANT_WRITE:
return PGRES_POLLING_WRITING;
case SSL_ERROR_SYSCALL:
{
char sebuf[256];
if (r == -1)
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("SSL SYSCALL error: %s\n"),
SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
else
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("SSL SYSCALL error: EOF detected\n"));
close_SSL(conn);
return PGRES_POLLING_FAILED;
}
case SSL_ERROR_SSL:
{
char *err = SSLerrmessage();
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("SSL error: %s\n"),
err);
SSLerrfree(err);
close_SSL(conn);
return PGRES_POLLING_FAILED;
}
default:
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("unrecognized SSL error code: %d\n"),
err);
close_SSL(conn);
return PGRES_POLLING_FAILED;
}
}
/*
* We already checked the server certificate in initialize_SSL() using
* SSL_CTX_set_verify(), if root.crt exists.
*/
/* get server certificate */
conn->peer = SSL_get_peer_certificate(conn->ssl);
if (conn->peer == NULL)
{
char *err = SSLerrmessage();
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("certificate could not be obtained: %s\n"),
err);
SSLerrfree(err);
close_SSL(conn);
return PGRES_POLLING_FAILED;
}
if (!verify_peer_name_matches_certificate(conn))
{
close_SSL(conn);
return PGRES_POLLING_FAILED;
}
/* SSL handshake is complete */
return PGRES_POLLING_OK;
}
/*
* Close SSL connection.
*/
static void
close_SSL(PGconn *conn)
{
bool destroy_needed = false;
if (conn->ssl)
{
DECLARE_SIGPIPE_INFO(spinfo);
/*
* We can't destroy everything SSL-related here due to the possible
* later calls to OpenSSL routines which may need our thread
* callbacks, so set a flag here and check at the end.
*/
destroy_needed = true;
DISABLE_SIGPIPE(conn, spinfo, (void) 0);
SSL_shutdown(conn->ssl);
SSL_free(conn->ssl);
conn->ssl = NULL;
/* We have to assume we got EPIPE */
REMEMBER_EPIPE(spinfo, true);
RESTORE_SIGPIPE(conn, spinfo);
}
if (conn->peer)
{
X509_free(conn->peer);
conn->peer = NULL;
}
#ifdef USE_SSL_ENGINE
if (conn->engine)
{
ENGINE_finish(conn->engine);
ENGINE_free(conn->engine);
conn->engine = NULL;
}
#endif
/*
* This will remove our SSL locking hooks, if this is the last SSL
* connection, which means we must wait to call it until after all SSL
* calls have been made, otherwise we can end up with a race condition and
* possible deadlocks.
*
* See comments above destroy_ssl_system().
*/
if (destroy_needed)
pqsecure_destroy();
}
/*
* 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 char ssl_nomem[] = "out of memory allocating error description";
#define SSL_ERR_LEN 128
static char *
SSLerrmessage(void)
{
unsigned long errcode;
const char *errreason;
char *errbuf;
errbuf = malloc(SSL_ERR_LEN);
if (!errbuf)
return ssl_nomem;
errcode = ERR_get_error();
if (errcode == 0)
{
snprintf(errbuf, SSL_ERR_LEN, libpq_gettext("no SSL error reported"));
return errbuf;
}
errreason = ERR_reason_error_string(errcode);
if (errreason != NULL)
{
strlcpy(errbuf, errreason, SSL_ERR_LEN);
return errbuf;
}
snprintf(errbuf, SSL_ERR_LEN, libpq_gettext("SSL error code %lu"), errcode);
return errbuf;
}
static void
SSLerrfree(char *buf)
{
if (buf != ssl_nomem)
free(buf);
}
/*
* Return pointer to OpenSSL object.
*/
void *
PQgetssl(PGconn *conn)
{
if (!conn)
return NULL;
return conn->ssl;
}
#else /* !USE_SSL */
#ifndef USE_SSL
void *
PQgetssl(PGconn *conn)
{
......
......@@ -73,14 +73,14 @@ typedef struct
#endif
#endif /* ENABLE_SSPI */
#ifdef USE_SSL
#ifdef USE_OPENSSL
#include <openssl/ssl.h>
#include <openssl/err.h>
#if (SSLEAY_VERSION_NUMBER >= 0x00907000L) && !defined(OPENSSL_NO_ENGINE)
#define USE_SSL_ENGINE
#endif
#endif /* USE_SSL */
#endif /* USE_OPENSSL */
/*
* POSTGRES backend dependent Constants.
......@@ -427,6 +427,8 @@ struct pg_conn
bool allow_ssl_try; /* Allowed to try SSL negotiation */
bool wait_ssl_try; /* Delay SSL negotiation until after
* attempting normal connection */
bool ssl_in_use;
#ifdef USE_OPENSSL
SSL *ssl; /* SSL status, if have SSL connection */
X509 *peer; /* X509 cert of server */
#ifdef USE_SSL_ENGINE
......@@ -435,6 +437,7 @@ struct pg_conn
void *engine; /* dummy field to keep struct the same if
* OpenSSL version changes */
#endif
#endif /* USE_OPENSSL */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
......@@ -482,6 +485,24 @@ struct pg_cancel
*/
extern char *const pgresStatus[];
#ifdef USE_SSL
#ifndef WIN32
#define USER_CERT_FILE ".postgresql/postgresql.crt"
#define USER_KEY_FILE ".postgresql/postgresql.key"
#define ROOT_CERT_FILE ".postgresql/root.crt"
#define ROOT_CRL_FILE ".postgresql/root.crl"
#else
/* On Windows, the "home" directory is already PostgreSQL-specific */
#define USER_CERT_FILE "postgresql.crt"
#define USER_KEY_FILE "postgresql.key"
#define ROOT_CERT_FILE "root.crt"
#define ROOT_CRL_FILE "root.crl"
#endif
#endif /* USE_SSL */
/* ----------------
* Internal functions of libpq
* Functions declared here need to be visible across files of libpq,
......@@ -603,6 +624,8 @@ extern PostgresPollingStatusType pqsecure_open_client(PGconn *);
extern void pqsecure_close(PGconn *);
extern ssize_t pqsecure_read(PGconn *, void *ptr, size_t len);
extern ssize_t pqsecure_write(PGconn *, const void *ptr, size_t len);
extern ssize_t pqsecure_raw_read(PGconn *, void *ptr, size_t len);
extern ssize_t pqsecure_raw_write(PGconn *, const void *ptr, size_t len);
#if defined(ENABLE_THREAD_SAFETY) && !defined(WIN32)
extern int pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending);
......@@ -610,6 +633,16 @@ extern void pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending,
bool got_epipe);
#endif
/*
* The SSL implementatation provides these functions (fe-secure-openssl.c)
*/
extern void pgtls_init_library(bool do_ssl, int do_crypto);
extern int pgtls_init(PGconn *conn);
extern PostgresPollingStatusType pgtls_open_client(PGconn *conn);
extern void pgtls_close(PGconn *conn);
extern ssize_t pgtls_read(PGconn *conn, void *ptr, size_t len);
extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
/*
* this is so that we can check if a connection is non-blocking internally
* without the overhead of a function call
......
......@@ -2,7 +2,7 @@
# Will build a static library libpq(d).lib
# and a dynamic library libpq(d).dll with import library libpq(d)dll.lib
# USE_SSL=1 will compile with OpenSSL
# USE_OPENSSL=1 will compile with OpenSSL
# USE_KFW=1 will compile with kfw(kerberos for Windows)
# DEBUG=1 compiles with debugging symbols
# ENABLE_THREAD_SAFETY=1 compiles with threading enabled
......@@ -124,6 +124,9 @@ CLEAN :
-@erase "$(OUTDIR)\$(OUTFILENAME).dll.manifest"
-@erase "$(OUTDIR)\*.idb"
-@erase pg_config_paths.h"
!IFDEF USE_OPENSSL
-@erase "$(INTDIR)\fe-secure-openssl.obj"
!ENDIF
LIB32=link.exe -lib
......@@ -164,6 +167,9 @@ LIB32_OBJS= \
"$(INTDIR)\win32error.obj" \
"$(INTDIR)\win32setlocale.obj" \
"$(INTDIR)\pthread-win32.obj"
!IFDEF USE_OPENSSL
LIB32_OBJS=$(LIB32_OBJS) "$(INTDIR)\fe-secure-openssl.obj"
!ENDIF
config: ..\..\include\pg_config.h ..\..\include\pg_config_ext.h pg_config_paths.h ..\..\include\pg_config_os.h
......@@ -189,8 +195,8 @@ CPP_PROJ=/nologo /W3 /EHsc $(OPT) /I "..\..\include" /I "..\..\include\port\win3
/Fo"$(INTDIR)\\" /Fd"$(INTDIR)\\" /FD /c \
/D "_CRT_SECURE_NO_DEPRECATE" $(ADD_DEFINES)
!IFDEF USE_SSL
CPP_PROJ=$(CPP_PROJ) /D USE_SSL
!IFDEF USE_OPENSSL
CPP_PROJ=$(CPP_PROJ) /D USE_OPENSSL
SSL_LIBS=ssleay32.lib libeay32.lib gdi32.lib
!ENDIF
......
......@@ -117,6 +117,12 @@ sub mkvcbuild
$postgres->AddLibrary('ws2_32.lib');
$postgres->AddLibrary('wldap32.lib') if ($solution->{options}->{ldap});
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove be-secure-openssl.c
# if building without OpenSSL
if (!$solution->{options}->{openssl})
{
$postgres->RemoveFile('src\backend\libpq\be-secure-openssl.c');
}
my $snowball = $solution->AddProject('dict_snowball', 'dll', '',
'src\backend\snowball');
......@@ -276,6 +282,12 @@ sub mkvcbuild
$libpq->ReplaceFile('src\interfaces\libpq\libpqrc.c',
'src\interfaces\libpq\libpq.rc');
$libpq->AddReference($libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove fe-secure-openssl.c
# if building without OpenSSL
if (!$solution->{options}->{openssl})
{
$libpq->RemoveFile('src\interfaces\libpq\fe-secure-openssl.c');
}
my $libpqwalreceiver =
$solution->AddProject('libpqwalreceiver', 'dll', '',
......
......@@ -182,7 +182,7 @@ sub GenerateFiles
if ($self->{options}->{integer_datetimes});
print O "#define USE_LDAP 1\n" if ($self->{options}->{ldap});
print O "#define HAVE_LIBZ 1\n" if ($self->{options}->{zlib});
print O "#define USE_SSL 1\n" if ($self->{options}->{openssl});
print O "#define USE_OPENSSL 1\n" if ($self->{options}->{openssl});
print O "#define ENABLE_NLS 1\n" if ($self->{options}->{nls});
print O "#define BLCKSZ ", 1024 * $self->{options}->{blocksize}, "\n";
......@@ -628,7 +628,7 @@ sub GetFakeConfigure
$cfg .= ' --with-ldap' if ($self->{options}->{ldap});
$cfg .= ' --without-zlib' unless ($self->{options}->{zlib});
$cfg .= ' --with-extra-version' if ($self->{options}->{extraver});
$cfg .= ' --with-openssl' if ($self->{options}->{ssl});
$cfg .= ' --with-openssl' if ($self->{options}->{openssl});
$cfg .= ' --with-ossp-uuid' if ($self->{options}->{uuid});
$cfg .= ' --with-libxml' if ($self->{options}->{xml});
$cfg .= ' --with-libxslt' if ($self->{options}->{xslt});
......
......@@ -16,7 +16,7 @@ our $config = {
tcl => undef, # --with-tls=<path>
perl => undef, # --with-perl
python => undef, # --with-python=<path>
openssl => undef, # --with-ssl=<path>
openssl => undef, # --with-openssl=<path>
uuid => undef, # --with-ossp-uuid
xml => undef, # --with-libxml=<path>
xslt => undef, # --with-libxslt=<path>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment