mirror of
https://github.com/postgres/postgres.git
synced 2025-04-22 23:02:54 +03:00
attacks where an attacker would put <attack>\0<propername> in the field and trick the validation code that the certificate was for <attack>. This is a very low risk attack since it reuqires the attacker to trick the CA into issuing a certificate with an incorrect field, and the common PostgreSQL deployments are with private CAs, and not external ones. Also, default mode in 8.4 does not do any name validation, and is thus also not vulnerable - but the higher security modes are. Backpatch all the way. Even though versions 8.3.x and before didn't have certificate name validation support, they still exposed this field for the user to perform the validation in the application code, and there is no way to detect this problem through that API. Security: CVE-2009-4034
1034 lines
26 KiB
C
1034 lines
26 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* be-secure.c
|
|
* functions related to setting up a secure connection to the frontend.
|
|
* Secure connections are expected to provide confidentiality,
|
|
* message integrity and endpoint authentication.
|
|
*
|
|
*
|
|
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* $PostgreSQL: pgsql/src/backend/libpq/be-secure.c,v 1.93 2009/12/09 06:37:06 mha Exp $
|
|
*
|
|
* 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. Empheral
|
|
* 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
|
|
|
|
#ifdef USE_SSL
|
|
#include <openssl/ssl.h>
|
|
#include <openssl/dh.h>
|
|
#if SSLEAY_VERSION_NUMBER >= 0x0907000L
|
|
#include <openssl/conf.h>
|
|
#endif
|
|
#endif /* USE_SSL */
|
|
|
|
#include "libpq/libpq.h"
|
|
#include "tcop/tcopprot.h"
|
|
|
|
|
|
#ifdef USE_SSL
|
|
|
|
#define ROOT_CERT_FILE "root.crt"
|
|
#define ROOT_CRL_FILE "root.crl"
|
|
#define SERVER_CERT_FILE "server.crt"
|
|
#define SERVER_PRIVATE_KEY_FILE "server.key"
|
|
|
|
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
|
|
|
|
#ifdef USE_SSL
|
|
/*
|
|
* How much data can be sent across a secure connection
|
|
* (total in both directions) before we require renegotiation.
|
|
*/
|
|
#define RENEGOTIATION_LIMIT (512 * 1024 * 1024)
|
|
|
|
static SSL_CTX *SSL_context = NULL;
|
|
static bool ssl_loaded_verify_locations = false;
|
|
|
|
/* GUC variable controlling SSL cipher list */
|
|
char *SSLCipherSuites = NULL;
|
|
#endif
|
|
|
|
/* ------------------------------------------------------------ */
|
|
/* Hardcoded values */
|
|
/* ------------------------------------------------------------ */
|
|
|
|
/*
|
|
* Hardcoded DH parameters, used in empheral 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 */
|
|
/* ------------------------------------------------------------ */
|
|
|
|
/*
|
|
* Initialize global context
|
|
*/
|
|
int
|
|
secure_initialize(void)
|
|
{
|
|
#ifdef USE_SSL
|
|
initialize_SSL();
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Indicate if we have loaded the root CA store to verify certificates
|
|
*/
|
|
bool
|
|
secure_loaded_verify_locations(void)
|
|
{
|
|
#ifdef USE_SSL
|
|
return ssl_loaded_verify_locations;
|
|
#endif
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Attempt to negotiate secure session.
|
|
*/
|
|
int
|
|
secure_open_server(Port *port)
|
|
{
|
|
int r = 0;
|
|
|
|
#ifdef USE_SSL
|
|
r = open_server_SSL(port);
|
|
#endif
|
|
|
|
return r;
|
|
}
|
|
|
|
/*
|
|
* Close secure session.
|
|
*/
|
|
void
|
|
secure_close(Port *port)
|
|
{
|
|
#ifdef USE_SSL
|
|
if (port->ssl)
|
|
close_SSL(port);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Read data from a secure connection.
|
|
*/
|
|
ssize_t
|
|
secure_read(Port *port, void *ptr, size_t len)
|
|
{
|
|
ssize_t n;
|
|
|
|
#ifdef USE_SSL
|
|
if (port->ssl)
|
|
{
|
|
int err;
|
|
|
|
rloop:
|
|
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:
|
|
#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)));
|
|
n = -1;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
prepare_for_client_read();
|
|
|
|
n = recv(port->sock, ptr, len, 0);
|
|
|
|
client_read_ended();
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
/*
|
|
* Write data to a secure connection.
|
|
*/
|
|
ssize_t
|
|
secure_write(Port *port, void *ptr, size_t len)
|
|
{
|
|
ssize_t n;
|
|
|
|
#ifdef USE_SSL
|
|
if (port->ssl)
|
|
{
|
|
int err;
|
|
|
|
if (port->count > RENEGOTIATION_LIMIT)
|
|
{
|
|
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 renegotiation failure")));
|
|
if (SSL_do_handshake(port->ssl) <= 0)
|
|
ereport(COMMERROR,
|
|
(errcode(ERRCODE_PROTOCOL_VIOLATION),
|
|
errmsg("SSL renegotiation failure")));
|
|
if (port->ssl->state != SSL_ST_OK)
|
|
ereport(COMMERROR,
|
|
(errcode(ERRCODE_PROTOCOL_VIOLATION),
|
|
errmsg("SSL failed to send renegotiation request")));
|
|
port->ssl->state |= SSL_ST_ACCEPT;
|
|
SSL_do_handshake(port->ssl);
|
|
if (port->ssl->state != SSL_ST_OK)
|
|
ereport(COMMERROR,
|
|
(errcode(ERRCODE_PROTOCOL_VIOLATION),
|
|
errmsg("SSL renegotiation failure")));
|
|
port->count = 0;
|
|
}
|
|
|
|
wloop:
|
|
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)));
|
|
n = -1;
|
|
break;
|
|
}
|
|
}
|
|
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.
|
|
*
|
|
* They are closely modelled on the original socket implementations in OpenSSL.
|
|
*
|
|
*/
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
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 empheral 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;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Initialize global SSL context.
|
|
*/
|
|
static void
|
|
initialize_SSL(void)
|
|
{
|
|
struct stat buf;
|
|
|
|
if (!SSL_context)
|
|
{
|
|
#if SSLEAY_VERSION_NUMBER >= 0x0907000L
|
|
OPENSSL_config(NULL);
|
|
#endif
|
|
SSL_library_init();
|
|
SSL_load_error_strings();
|
|
SSL_context = SSL_CTX_new(SSLv23_method());
|
|
if (!SSL_context)
|
|
ereport(FATAL,
|
|
(errmsg("could not create SSL context: %s",
|
|
SSLerrmessage())));
|
|
|
|
/*
|
|
* Load and verify certificate and private key
|
|
*/
|
|
if (SSL_CTX_use_certificate_chain_file(SSL_context,
|
|
SERVER_CERT_FILE) != 1)
|
|
ereport(FATAL,
|
|
(errcode(ERRCODE_CONFIG_FILE_ERROR),
|
|
errmsg("could not load server certificate file \"%s\": %s",
|
|
SERVER_CERT_FILE, SSLerrmessage())));
|
|
|
|
if (stat(SERVER_PRIVATE_KEY_FILE, &buf) != 0)
|
|
ereport(FATAL,
|
|
(errcode_for_file_access(),
|
|
errmsg("could not access private key file \"%s\": %m",
|
|
SERVER_PRIVATE_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",
|
|
SERVER_PRIVATE_KEY_FILE),
|
|
errdetail("Permissions should be u=rw (0600) or less.")));
|
|
#endif
|
|
|
|
if (SSL_CTX_use_PrivateKey_file(SSL_context,
|
|
SERVER_PRIVATE_KEY_FILE,
|
|
SSL_FILETYPE_PEM) != 1)
|
|
ereport(FATAL,
|
|
(errmsg("could not load private key file \"%s\": %s",
|
|
SERVER_PRIVATE_KEY_FILE, SSLerrmessage())));
|
|
|
|
if (SSL_CTX_check_private_key(SSL_context) != 1)
|
|
ereport(FATAL,
|
|
(errmsg("check of private key failed: %s",
|
|
SSLerrmessage())));
|
|
}
|
|
|
|
/* set up empheral DH keys */
|
|
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);
|
|
|
|
/* setup 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)");
|
|
|
|
/*
|
|
* Attempt to load CA store, so we can verify client certificates if
|
|
* needed.
|
|
*/
|
|
if (access(ROOT_CERT_FILE, R_OK))
|
|
{
|
|
ssl_loaded_verify_locations = false;
|
|
|
|
/*
|
|
* If root certificate file simply not found. Don't log an error here,
|
|
* because it's quite likely the user isn't planning on using client
|
|
* certificates. If we can't access it for other reasons, it is an
|
|
* error.
|
|
*/
|
|
if (errno != ENOENT)
|
|
{
|
|
ereport(FATAL,
|
|
(errmsg("could not access root certificate file \"%s\": %m",
|
|
ROOT_CERT_FILE)));
|
|
}
|
|
}
|
|
else if (SSL_CTX_load_verify_locations(SSL_context, ROOT_CERT_FILE, NULL) != 1)
|
|
{
|
|
/*
|
|
* File was there, but we could not load it. This means the file is
|
|
* somehow broken, and we cannot do verification at all - so abort
|
|
* here.
|
|
*/
|
|
ssl_loaded_verify_locations = false;
|
|
ereport(FATAL,
|
|
(errmsg("could not load root certificate file \"%s\": %s",
|
|
ROOT_CERT_FILE, SSLerrmessage())));
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Check the Certificate Revocation List (CRL) if file exists.
|
|
* http://searchsecurity.techtarget.com/sDefinition/0,,sid14_gci803160,
|
|
* 00.html
|
|
*/
|
|
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, ROOT_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",
|
|
ROOT_CRL_FILE),
|
|
errdetail("SSL library does not support certificate revocation lists.")));
|
|
#endif
|
|
else
|
|
{
|
|
/* Not fatal - we do not require CRL */
|
|
ereport(LOG,
|
|
(errmsg("SSL certificate revocation list file \"%s\" not found, skipping: %s",
|
|
ROOT_CRL_FILE, SSLerrmessage()),
|
|
errdetail("Certificates will not be checked against revocation list.")));
|
|
}
|
|
|
|
/*
|
|
* Always ask for SSL client cert, but don't fail if it's not
|
|
* presented. We'll fail later in this case, based on what we find
|
|
* in pg_hba.conf.
|
|
*/
|
|
SSL_CTX_set_verify(SSL_context,
|
|
(SSL_VERIFY_PEER |
|
|
SSL_VERIFY_CLIENT_ONCE),
|
|
verify_cb);
|
|
|
|
ssl_loaded_verify_locations = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
|
|
/* get client certificate, if available. */
|
|
port->peer = SSL_get_peer_certificate(port->ssl);
|
|
if (port->peer == NULL)
|
|
{
|
|
strlcpy(port->peer_dn, "(anonymous)", sizeof(port->peer_dn));
|
|
strlcpy(port->peer_cn, "(anonymous)", sizeof(port->peer_cn));
|
|
}
|
|
else
|
|
{
|
|
X509_NAME_oneline(X509_get_subject_name(port->peer),
|
|
port->peer_dn, sizeof(port->peer_dn));
|
|
port->peer_dn[sizeof(port->peer_dn) - 1] = '\0';
|
|
r = X509_NAME_get_text_by_NID(X509_get_subject_name(port->peer),
|
|
NID_commonName, port->peer_cn, sizeof(port->peer_cn));
|
|
port->peer_cn[sizeof(port->peer_cn) - 1] = '\0';
|
|
if (r == -1)
|
|
{
|
|
/* Unable to get the CN, set it to blank so it can't be used */
|
|
port->peer_cn[0] = '\0';
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Reject embedded NULLs in certificate common name to prevent attacks like
|
|
* CVE-2009-4034.
|
|
*/
|
|
if (r != strlen(port->peer_cn))
|
|
{
|
|
ereport(COMMERROR,
|
|
(errcode(ERRCODE_PROTOCOL_VIOLATION),
|
|
errmsg("SSL certificate's common name contains embedded null")));
|
|
close_SSL(port);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
ereport(DEBUG2,
|
|
(errmsg("SSL connection from \"%s\"", port->peer_cn)));
|
|
|
|
/* 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;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
#endif /* USE_SSL */
|