#include "utils/memutils.h"
+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(Port *port, int fd);
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_ecdh(void);
static const char *SSLerrmessage(void);
/* are we in the middle of a renegotiation? */
KWbuHn491xNO25CQWMtem80uKw+pTnisBRF/454n1Jnhub144YRBoN8CAQI=\n\
-----END DH PARAMETERS-----\n";
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
/*
- * Write data to a secure connection.
+ * Initialize global SSL context.
*/
-ssize_t
-be_tls_write(Port *port, void *ptr, size_t len)
+void
+be_tls_init(void)
{
- ssize_t n;
- int err;
+ struct stat buf;
- /*
- * 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)
+ STACK_OF(X509_NAME) *root_cert_list = NULL;
+
+ if (!SSL_context)
{
- in_ssl_renegotiation = true;
+#if SSLEAY_VERSION_NUMBER >= 0x0907000L
+ OPENSSL_config(NULL);
+#endif
+ SSL_library_init();
+ SSL_load_error_strings();
/*
- * 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.
+ * 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_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;
+ SSL_context = SSL_CTX_new(SSLv23_method());
+ if (!SSL_context)
+ ereport(FATAL,
+ (errmsg("could not create SSL context: %s",
+ SSLerrmessage())));
- /*
- * 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")));
- }
- }
- }
+ /*
+ * 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);
-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;
- }
+ /*
+ * 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 (n >= 0)
- {
- /* is renegotiation complete? */
- if (in_ssl_renegotiation &&
- SSL_num_renegotiations(port->ssl) >= 1)
- {
- in_ssl_renegotiation = false;
- port->count = 0;
- }
+ 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)));
/*
- * 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.
+ * 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 (in_ssl_renegotiation &&
- port->count > ssl_renegotiation_limit * 1024L)
+#if !defined(WIN32) && !defined(__CYGWIN__)
+ if (!S_ISREG(buf.st_mode) || buf.st_mode & (S_IRWXG | S_IRWXO))
ereport(FATAL,
- (errcode(ERRCODE_PROTOCOL_VIOLATION),
- errmsg("SSL failed to renegotiate connection before limit expired")));
+ (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())));
}
- return n;
-}
+ /* 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);
-/* ------------------------------------------------------------ */
-/* OpenSSL specific code */
-/* ------------------------------------------------------------ */
+ /* set up ephemeral ECDH keys */
+ initialize_ecdh();
-/*
- * 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?
- */
+ /* 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)");
-static bool my_bio_initialized = false;
-static BIO_METHOD my_bio_methods;
+ /* Let server choose order */
+ if (SSLPreferServerCiphers)
+ SSL_CTX_set_options(SSL_context, SSL_OP_CIPHER_SERVER_PREFERENCE);
-static int
-my_sock_read(BIO *h, char *buf, int size)
-{
- int res = 0;
+ /*
+ * 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())));
+ }
- if (buf != NULL)
+ /*----------
+ * Load the Certificate Revocation List (CRL).
+ * http://searchsecurity.techtarget.com/sDefinition/0,,sid14_gci803160,00.html
+ *----------
+ */
+ if (ssl_crl_file[0])
{
- res = secure_raw_read(((Port *)h->ptr), buf, size);
- BIO_clear_retry_flags(h);
- if (res <= 0)
+ X509_STORE *cvstore = SSL_CTX_get_cert_store(SSL_context);
+
+ if (cvstore)
{
- /* If we were interrupted, tell caller to retry */
- if (errno == EINTR)
+ /* Set the flags to check against the complete CRL chain */
+ if (X509_STORE_load_locations(cvstore, ssl_crl_file, NULL) == 1)
{
- BIO_set_retry_read(h);
+ /* 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())));
+ }
+ }
- return res;
+ 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);
+ }
}
-static int
-my_sock_write(BIO *h, const char *buf, int size)
+/*
+ * Attempt to negotiate SSL connection.
+ */
+int
+be_tls_open_server(Port *port)
{
- int res = 0;
+ int r;
+ int err;
- res = secure_raw_write(((Port *) h->ptr), buf, size);
- BIO_clear_retry_flags(h);
- if (res <= 0)
+ Assert(!port->ssl);
+ Assert(!port->peer);
+
+ if (!(port->ssl = SSL_new(SSL_context)))
{
- if (errno == EINTR)
+ 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)
{
- BIO_set_retry_write(h);
+ 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;
}
- return res;
-}
+ port->count = 0;
-static BIO_METHOD *
-my_BIO_s_socket(void)
-{
- if (!my_bio_initialized)
+ /* 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)
{
- 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;
-}
+ int len;
-/* 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;
+ len = X509_NAME_get_text_by_NID(X509_get_subject_name(port->peer),
+ NID_commonName, NULL, 0);
+ if (len != -1)
+ {
+ char *peer_cn;
- bio = BIO_new(my_BIO_s_socket());
+ 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;
+ }
- if (bio == NULL)
- {
- SSLerr(SSL_F_SSL_SET_FD, ERR_R_BUF_LIB);
- goto err;
+ /*
+ * 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;
}
- /* 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;
+ 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;
}
/*
- * 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.
+ * Close SSL connection.
*/
-static DH *
-load_dh_file(int keylength)
+void
+be_tls_close(Port *port)
{
- 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)
+ if (port->ssl)
{
- elog(LOG, "DH errors (%s): %d bits expected, %d bits found",
- fnbuf, keylength, 8 * DH_size(dh));
- dh = NULL;
+ SSL_shutdown(port->ssl);
+ SSL_free(port->ssl);
+ port->ssl = NULL;
+ port->ssl_in_use = false;
}
- /* make sure the DH parameters are usable */
- if (dh != NULL)
+ if (port->peer)
{
- 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;
- }
+ X509_free(port->peer);
+ port->peer = NULL;
}
- return dh;
+ if (port->peer_cn)
+ {
+ pfree(port->peer_cn);
+ port->peer_cn = NULL;
+ }
}
/*
- * 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.
+ * Read data from a secure connection.
*/
-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)
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len)
{
- DH *r = NULL;
- static DH *dh = NULL;
- static DH *dh512 = NULL;
- static DH *dh1024 = NULL;
- static DH *dh2048 = NULL;
- static DH *dh4096 = NULL;
+ ssize_t n;
+ int err;
- switch (keylength)
+rloop:
+ errno = 0;
+ n = SSL_read(port->ssl, ptr, len);
+ err = SSL_get_error(port->ssl, n);
+ switch (err)
{
- 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;
+ case SSL_ERROR_NONE:
+ port->count += n;
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;
+ 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 4096:
- if (dh4096 == NULL)
- dh4096 = load_dh_file(keylength);
- if (dh4096 == NULL)
- dh4096 = load_dh_buffer(file_dh4096, sizeof file_dh4096);
- r = dh4096;
+ 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:
- 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)));
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("unrecognized SSL error code: %d",
+ err)));
+ errno = ECONNRESET;
+ n = -1;
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);
+ return n;
}
-#else
-#define initialize_ecdh()
-#endif
/*
- * Initialize global SSL context.
+ * Write data to a secure connection.
*/
-void
-be_tls_init(void)
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len)
{
- struct stat buf;
-
- STACK_OF(X509_NAME) *root_cert_list = NULL;
+ ssize_t n;
+ int err;
- if (!SSL_context)
+ /*
+ * 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)
{
-#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)));
+ in_ssl_renegotiation = true;
/*
- * Require no public access to key file.
+ * 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.
*
- * 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)
+ * OpenSSL provides SSL_renegotiation_pending(), but this doesn't
+ * seem to work in testing.
*/
-#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)");
+ SSL_clear_num_renegotiations(port->ssl);
- /* Let server choose order */
- if (SSLPreferServerCiphers)
- SSL_CTX_set_options(SSL_context, SSL_OP_CIPHER_SERVER_PREFERENCE);
+ 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;
- /*
- * 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())));
+ /*
+ * 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")));
+ }
+ }
}
- /*----------
- * Load the Certificate Revocation List (CRL).
- * http://searchsecurity.techtarget.com/sDefinition/0,,sid14_gci803160,00.html
- *----------
- */
- if (ssl_crl_file[0])
+wloop:
+ errno = 0;
+ n = SSL_write(port->ssl, ptr, len);
+ err = SSL_get_error(port->ssl, n);
+ switch (err)
{
- 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.")));
+ 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;
}
- else
- ereport(FATAL,
- (errmsg("could not load SSL certificate revocation list file \"%s\": %s",
- ssl_crl_file, SSLerrmessage())));
- }
+ 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 (ssl_ca_file[0])
+ if (n >= 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;
+ /* is renegotiation complete? */
+ if (in_ssl_renegotiation &&
+ SSL_num_renegotiations(port->ssl) >= 1)
+ {
+ in_ssl_renegotiation = false;
+ port->count = 0;
+ }
/*
- * 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.
+ * 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.
*/
- SSL_CTX_set_client_CA_list(SSL_context, root_cert_list);
+ 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;
}
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
/*
- * Attempt to negotiate SSL connection.
+ * 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?
*/
-int
-be_tls_open_server(Port *port)
-{
- int r;
- int err;
- Assert(!port->ssl);
- Assert(!port->peer);
+static bool my_bio_initialized = false;
+static BIO_METHOD my_bio_methods;
- 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))
+static int
+my_sock_read(BIO *h, char *buf, int size)
+{
+ int res = 0;
+
+ if (buf != NULL)
{
- ereport(COMMERROR,
- (errcode(ERRCODE_PROTOCOL_VIOLATION),
- errmsg("could not set SSL socket: %s",
- SSLerrmessage())));
- be_tls_close(port);
- return -1;
+ 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);
+ }
+ }
}
- port->ssl_in_use = true;
-aloop:
- r = SSL_accept(port->ssl);
- if (r <= 0)
+ 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)
{
- err = SSL_get_error(port->ssl, r);
- switch (err)
+ if (errno == EINTR)
{
- 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;
+ BIO_set_retry_write(h);
}
- be_tls_close(port);
- return -1;
}
- port->count = 0;
+ return res;
+}
- /* Get client certificate, if available. */
- port->peer = SSL_get_peer_certificate(port->ssl);
+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;
+}
- /* and extract the Common Name from it. */
- port->peer_cn = NULL;
- port->peer_cert_valid = false;
- if (port->peer != NULL)
+/* 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)
{
- int len;
+ SSLerr(SSL_F_SSL_SET_FD, ERR_R_BUF_LIB);
+ goto err;
+ }
+ /* Use 'ptr' to store pointer to PGconn */
+ bio->ptr = port;
- len = X509_NAME_get_text_by_NID(X509_get_subject_name(port->peer),
- NID_commonName, NULL, 0);
- if (len != -1)
- {
- char *peer_cn;
+ BIO_set_fd(bio, fd, BIO_NOCLOSE);
+ SSL_set_bio(port->ssl, bio, bio);
+ ret = 1;
+err:
+ return ret;
+}
- 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;
- }
+/*
+ * 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;
- /*
- * 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;
- }
+ /* 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;
+ }
- port->peer_cn = peer_cn;
+ /* 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;
}
- port->peer_cert_valid = true;
}
- ereport(DEBUG2,
- (errmsg("SSL connection from \"%s\"",
- port->peer_cn ? port->peer_cn : "(anonymous)")));
+ return dh;
+}
- /* set up debugging/info callback */
- SSL_CTX_set_info_callback(SSL_context, info_cb);
+/*
+ * 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;
- return 0;
+ 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;
}
/*
- * Close SSL connection.
+ * 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.
*/
-void
-be_tls_close(Port *port)
+static DH *
+tmp_dh_cb(SSL *s, int is_export, int keylength)
{
- if (port->ssl)
- {
- SSL_shutdown(port->ssl);
- SSL_free(port->ssl);
- port->ssl = NULL;
- port->ssl_in_use = false;
- }
+ DH *r = NULL;
+ static DH *dh = NULL;
+ static DH *dh512 = NULL;
+ static DH *dh1024 = NULL;
+ static DH *dh2048 = NULL;
+ static DH *dh4096 = NULL;
- if (port->peer)
+ switch (keylength)
{
- X509_free(port->peer);
- port->peer = NULL;
+ 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;
}
- if (port->peer_cn)
+ /* this may take a long time, but it may be necessary... */
+ if (r == NULL || 8 * DH_size(r) < keylength)
{
- pfree(port->peer_cn);
- port->peer_cn = NULL;
+ ereport(DEBUG2,
+ (errmsg_internal("DH: generating parameters (%d bits)",
+ keylength)));
+ r = DH_generate_parameters(keylength, DH_GENERATOR_2, NULL, NULL);
}
+
+ return r;
}
-ssize_t
-be_tls_read(Port *port, void *ptr, size_t len)
+/*
+ * 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)
{
- ssize_t n;
- int err;
+ return ok;
+}
-rloop:
- errno = 0;
- n = SSL_read(port->ssl, ptr, len);
- err = SSL_get_error(port->ssl, n);
- switch (err)
+/*
+ * 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_ERROR_NONE:
- port->count += n;
+ case SSL_CB_HANDSHAKE_START:
+ ereport(DEBUG4,
+ (errmsg_internal("SSL: handshake start")));
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;
- }
+ case SSL_CB_HANDSHAKE_DONE:
+ ereport(DEBUG4,
+ (errmsg_internal("SSL: handshake done")));
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;
+ case SSL_CB_ACCEPT_LOOP:
+ ereport(DEBUG4,
+ (errmsg_internal("SSL: accept loop")));
break;
- default:
- ereport(COMMERROR,
- (errcode(ERRCODE_PROTOCOL_VIOLATION),
- errmsg("unrecognized SSL error code: %d",
- err)));
- errno = ECONNRESET;
- n = -1;
+ 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;
+static void
+initialize_ecdh(void)
+{
+#if (OPENSSL_VERSION_NUMBER >= 0x0090800fL) && !defined(OPENSSL_NO_ECDH)
+ 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);
+#endif
}
/*