ssl-proxy-openssl.c revision fe791e96fdf796f7d8997ee0515b163dc5eddd72
/* Copyright (c) 2002-2016 Dovecot authors, see the included COPYING file */
#include "login-common.h"
#include "array.h"
#include "ioloop.h"
#include "net.h"
#include "ostream.h"
#include "read-full.h"
#include "safe-memset.h"
#include "hash.h"
#include "llist.h"
#include "master-interface.h"
#include "master-service-ssl-settings.h"
#include "client-common.h"
#include "ssl-proxy.h"
#include <fcntl.h>
#include <unistd.h>
#ifdef HAVE_OPENSSL
#include "iostream-openssl.h"
# define HAVE_ECDH
#endif
#ifndef SSL_CTRL_SET_TLSEXT_HOSTNAME /* FIXME: this may be unnecessary.. */
#endif
enum ssl_io_action {
};
struct ssl_proxy {
int refcount;
const struct login_settings *login_set;
const struct master_service_ssl_settings *ssl_set;
unsigned char plainout_buf[1024];
unsigned int plainout_size;
unsigned char sslout_buf[1024];
unsigned int sslout_size;
void *handshake_context;
const char *cert_error;
char *last_error;
bool handshaked:1;
bool destroyed:1;
bool cert_received:1;
bool cert_broken:1;
bool client_proxy:1;
bool flushing:1;
bool failed:1;
};
struct ssl_parameters {
DH *dh_default;
};
struct ssl_server_context {
const char *cert;
const char *key;
const char *ca;
const char *dh;
const char *cipher_list;
const char *protocols;
bool verify_client_cert;
bool prefer_server_ciphers;
bool compression;
bool tickets;
};
static int extdata_index;
static HASH_TABLE(struct ssl_server_context *,
struct ssl_server_context *) ssl_servers;
static SSL_CTX *ssl_client_ctx;
static unsigned int ssl_proxy_count;
static struct ssl_proxy *ssl_proxies;
static struct ssl_parameters ssl_params;
static int ssl_username_nid;
static ENGINE *ssl_engine;
static struct ssl_server_context *
const struct master_service_ssl_settings *ssl_set);
const struct master_service_ssl_settings *set);
#if defined(HAVE_ECDH) && !defined(SSL_CTRL_SET_ECDH_AUTO)
#endif
{
}
{
unsigned int i, g, h = 0;
/* checking for different certs is typically good enough,
and it should be enough to check only the first few bytes. */
if ((g = h & 0xf0000000UL) != 0) {
h = h ^ (g >> 24);
h = h ^ g;
}
}
return h;
}
const struct ssl_server_context *ctx2)
{
return 1;
return 1;
return 1;
return 1;
return 1;
}
{
}
}
{
switch (action) {
case SSL_ADD_INPUT:
break;
break;
case SSL_REMOVE_INPUT:
break;
case SSL_ADD_OUTPUT:
break;
break;
case SSL_REMOVE_OUTPUT:
break;
}
}
{
if (block) {
} else {
plain_read, proxy);
}
}
}
{
/* buffer full, block input until it's written */
return;
}
sizeof(proxy->sslout_buf) -
proxy->sslout_size);
if (ret <= 0) {
if (ret < 0)
break;
} else {
if (!corked) {
}
}
}
if (corked)
}
{
if (ret < 0)
else {
if (proxy->plainout_size > 0) {
plain_write, proxy);
}
} else {
}
}
}
const char *func_name)
{
int err;
switch (err) {
case SSL_ERROR_WANT_READ:
break;
case SSL_ERROR_WANT_WRITE:
break;
case SSL_ERROR_SYSCALL:
/* eat up the error queue */
if (ERR_peek_error() != 0)
else if (ret != 0)
else {
/* EOF. */
errstr = "Disconnected";
break;
}
break;
case SSL_ERROR_ZERO_RETURN:
/* clean connection closing */
break;
case SSL_ERROR_SSL:
i_error("OpenSSL malloc() failed. "
"You may need to increase service %s { vsz_limit }",
}
break;
default:
break;
}
}
}
{
int ret;
if (proxy->client_proxy) {
if (ret != 1) {
return;
}
} else {
if (ret != 1) {
return;
}
}
}
}
{
int ret;
sizeof(proxy->plainout_buf) -
if (ret <= 0) {
break;
} else {
}
}
}
{
int ret;
if (ret <= 0)
else {
proxy->sslout_size);
}
}
{
if (!proxy->handshaked) {
}
if (proxy->handshaked) {
else
if (proxy->sslout_size == 0)
else {
}
}
}
static int
const struct master_service_ssl_settings *ssl_set,
{
int sfd[2];
if (!ssl_initialized) {
i_error("SSL support not enabled in configuration");
return -1;
}
return -1;
}
return -1;
}
i_error("socketpair() failed: %m");
return -1;
}
return sfd[1];
}
static struct ssl_server_context *
const struct master_service_ssl_settings *set)
{
return ctx;
}
const struct login_settings *login_set,
const struct master_service_ssl_settings *ssl_set,
{
struct ssl_server_context *ctx;
}
const struct login_settings *login_set,
const struct master_service_ssl_settings *ssl_set,
{
int ret;
if (ret < 0)
return -1;
return ret;
}
{
}
{
}
{
}
{
}
{
}
{
char *name;
int len;
return NULL;
return NULL; /* we should have had it.. */
ssl_username_nid, NULL, 0);
if (len < 0)
name = "";
else {
name = "";
/* NUL characters in name. Someone's trying to fake
being another user? Don't allow it. */
name = "";
}
}
}
{
return proxy->handshaked;
}
{
return proxy->last_error;
}
{
const SSL_CIPHER *cipher;
const char *comp_str;
if (!proxy->handshaked)
return "";
return t_strdup_printf("%s with cipher %s (%d/%d bits)%s",
}
{
#if defined(HAVE_SSL_COMPRESSION) && !defined(OPENSSL_NO_COMP)
const COMP_METHOD *comp;
#else
return NULL;
#endif
}
{
"(Unknown error)";
}
{
}
{
return;
}
{
/* this is pretty kludgy. mainly this is just for flushing the final
LOGOUT command output. */
}
{
return;
}
{
BN_set_word(e, RSA_F4);
BN_free(e);
return rsa;
}
{
return;
if ((where & SSL_CB_ALERT) != 0) {
switch (ret & 0xff) {
case SSL_AD_CLOSE_NOTIFY:
i_debug("SSL alert: %s [%s]",
break;
default:
i_warning("SSL alert: where=0x%x, ret=%d: %s %s [%s]",
break;
}
} else if (ret == 0) {
i_warning("SSL failed: where=0x%x: %s [%s]",
} else {
i_debug("SSL: where=0x%x, ret=%d: %s [%s]",
}
}
{
int ctxerr;
char buf[1024];
ctxerr == X509_V_ERR_CRL_HAS_EXPIRED)) {
/* no CRL given with the CA list. don't worry about it. */
preverify_ok = 1;
}
if (preverify_ok == 0)
}
if (preverify_ok != 0) {
"Valid certificate: %s", buf));
} else {
"Invalid certificate: %s: %s",
}
}
/* Return success anyway, because if ssl_require_client_cert=no we
could still allow authentication. */
return 1;
}
static int
void *userdata)
{
i_error("SSL private key file is password protected, "
"but password isn't given");
return 0;
}
return 0;
}
unsigned int ssl_proxy_get_count(void)
{
return ssl_proxy_count;
}
{
/* mostly just copy&pasted from X509_load_cert_crl_file() */
int i;
i_fatal("BIO_new_mem_buf() failed");
}
for(i = 0; i < sk_X509_INFO_num(inf); i++) {
}
}
}
}
bool load_xnames)
{
/* enable all SSL workarounds, except empty fragments as it
makes SSL more vulnerable against attacks */
#ifdef SSL_OP_NO_COMPRESSION
#endif
#ifdef SSL_OP_NO_TICKET
#endif
#ifdef SSL_MODE_RELEASE_BUFFERS
#endif
/* set trusted CA certs */
}
return xnames;
}
static void
{
#if defined(HAVE_ECDH) && !defined(SSL_CTRL_SET_ECDH_AUTO)
int nid;
const char *curve_name;
#endif
if (SSL_CTX_need_tmp_RSA(ssl_ctx) != 0)
#ifdef HAVE_ECDH
/* In the non-recommended situation where ECDH cipher suites are being
used instead of ECDHE, do not reuse the same ECDH key pair for
different sessions. This option improves forward secrecy. */
#ifdef SSL_CTRL_SET_ECDH_AUTO
/* OpenSSL >= 1.0.2 automatically handles ECDH temporary key parameter
selection. */
#else
/* For OpenSSL < 1.0.2, ECDH temporary key parameter selection must be
performed manually. Attempt to select the same curve as that used
in the server's private EC key file. Otherwise fall back to the
NIST P-384 (secp384r1) curve to be compliant with RFC 6460 when
AES-256 TLS cipher suites are in use. This fall back option does
however make Dovecot non-compliant with RFC 6460 which requires
curve NIST P-256 (prime256v1) be used when AES-128 TLS cipher
suites are in use. At least the non-compliance is in the form of
providing too much security rather than too little. */
/* Fall back option */
nid = NID_secp384r1;
}
i_debug("SSL: elliptic curve %s will be used for ECDH and"
" ECDHE key exchanges", curve_name);
}
#endif
#endif
}
static void
{
#if OPENSSL_VERSION_NUMBER >= 0x00907000L
#endif
/* set list of CA names that are sent to client */
}
{
char *dup_password;
i_fatal("BIO_new_mem_buf() failed");
i_fatal("Couldn't parse private ssl_key: %s",
}
return pkey;
}
static DH *
ssl_proxy_load_dh(const char *dhparam)
{
i_fatal("BIO_new_mem_buf() failed");
i_fatal("Couldn't parse DH parameters: %s",
return dh;
}
static void
const struct master_service_ssl_settings *set)
{
const char *password;
}
static void
const struct master_service_ssl_settings *set)
{
}
#if defined(HAVE_ECDH) && !defined(SSL_CTRL_SET_ECDH_AUTO)
static int
{
int nid = 0;
const char *password;
else {
/* clear errors added by the above calls */
}
return nid;
}
#endif
static int
{
/* mostly just copy&pasted from SSL_CTX_use_certificate_chain_file() */
X509 *x;
int ret = 0;
i_fatal("BIO_new_mem_buf() failed");
if (x == NULL)
goto end;
#if 0
/* This is in OpenSSL code, but it seems to cause failures.. */
if (ERR_peek_error() != 0)
ret = 0;
#endif
if (ret != 0) {
/* If we could set up our certificate, now proceed to
* the CA certificates.
*/
int r;
unsigned long err;
if (r == 0) {
ret = 0;
goto end;
}
}
/* When the while loop ends, it's usually just EOF. */
err = ERR_peek_last_error();
else
ret = 0; /* some real error */
}
end:
return ret;
}
#ifdef HAVE_SSL_GET_SERVERNAME
void *context ATTR_UNUSED)
{
struct ssl_server_context *ctx;
const char *host;
void **other_sets;
if (!client->ssl_servername_settings_read) {
&other_sets);
}
}
#endif
static struct ssl_server_context *
const struct master_service_ssl_settings *ssl_set)
{
struct ssl_server_context *ctx;
i_fatal("SSL_CTX_new() failed");
i_fatal("Can't set cipher list to '%s': %s",
}
if (ctx->prefer_server_ciphers)
i_fatal("Can't load ssl_cert: %s",
}
#ifdef HAVE_SSL_GET_SERVERNAME
ssl_servername_callback) != 1) {
if (ssl_set->verbose_ssl)
i_debug("OpenSSL library doesn't support SNI");
}
#endif
if (ctx->verify_client_cert)
return ctx;
}
{
}
static void
const struct login_settings *set)
{
return;
i_fatal("Can't load ssl_client_cert: %s",
}
i_fatal("Can't load private ssl_client_key: %s",
}
}
static void
const struct master_service_ssl_settings *ssl_set)
{
i_fatal("SSL_CTX_new() failed");
}
void ssl_proxy_init(void)
{
static char dovecot[] = "dovecot";
unsigned char buf;
return;
if (ssl_engine == NULL) {
i_fatal("Unknown ssl_crypto_device: %s",
}
}
if (ssl_username_nid == NID_undef) {
i_fatal("Invalid ssl_cert_username_field: %s",
}
does it before chrooting. We might not have enough entropy at
the first try, so this function may fail. It's still been
initialized though. */
ssl_proxy_count = 0;
ssl_proxies = NULL;
}
void ssl_proxy_deinit(void)
{
struct hash_iterate_context *iter;
struct ssl_server_context *ctx;
if (!ssl_initialized)
return;
while (ssl_proxies != NULL)
if (ssl_engine != NULL) {
}
EVP_cleanup();
}
#endif