ssl-proxy-openssl.c [plain text]
#include "login-common.h"
#include "array.h"
#include "ioloop.h"
#include "network.h"
#include "ostream.h"
#include "read-full.h"
#include "safe-memset.h"
#include "hash.h"
#include "llist.h"
#include "master-interface.h"
#include "client-common.h"
#include "ssl-proxy.h"
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#ifdef HAVE_OPENSSL
#include <openssl/crypto.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#ifdef APPLE_OS_X_SERVER
#include <signal.h>
#include <sys/wait.h>
#include "istream.h"
#include "fd-set-nonblock.h"
#include <Security/SecKeychain.h>
#include <Security/SecKeychainItem.h>
typedef struct
{
int len;
char key[ FILENAME_MAX ];
int reserved;
} CallbackUserData;
static CallbackUserData *s_user_data = NULL;
struct apple_password_ctx {
struct istream *from_certadmin;
struct ioloop *ioloop;
const char *reply;
unsigned int timed_out:1;
unsigned int overflow:1;
};
#endif
#define SSL_PARAMFILE_CHECK_INTERVAL (60*30)
#define SSL_PARAMETERS_PATH "ssl-params"
#ifndef SSL_CTRL_SET_TLSEXT_HOSTNAME
# undef HAVE_SSL_GET_SERVERNAME
#endif
enum ssl_io_action {
SSL_ADD_INPUT,
SSL_REMOVE_INPUT,
SSL_ADD_OUTPUT,
SSL_REMOVE_OUTPUT
};
struct ssl_proxy {
int refcount;
struct ssl_proxy *prev, *next;
SSL *ssl;
struct client *client;
struct ip_addr ip;
const struct login_settings *set;
int fd_ssl, fd_plain;
struct io *io_ssl_read, *io_ssl_write, *io_plain_read, *io_plain_write;
unsigned char plainout_buf[1024];
unsigned int plainout_size;
unsigned char sslout_buf[1024];
unsigned int sslout_size;
ssl_handshake_callback_t *handshake_callback;
void *handshake_context;
char *last_error;
unsigned int handshaked:1;
unsigned int destroyed:1;
unsigned int cert_received:1;
unsigned int cert_broken:1;
unsigned int client_proxy:1;
};
struct ssl_parameters {
const char *path;
time_t last_refresh;
int fd;
DH *dh_512, *dh_1024;
};
struct ssl_server_context {
SSL_CTX *ctx;
pool_t pool;
const char *cert;
const char *key;
const char *ca;
const char *cipher_list;
bool verify_client_cert;
};
static int extdata_index;
static struct hash_table *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 void plain_read(struct ssl_proxy *proxy);
static void ssl_read(struct ssl_proxy *proxy);
static void ssl_write(struct ssl_proxy *proxy);
static void ssl_step(struct ssl_proxy *proxy);
static void ssl_proxy_destroy(struct ssl_proxy *proxy);
static void ssl_proxy_unref(struct ssl_proxy *proxy);
static struct ssl_server_context *
ssl_server_context_init(const struct login_settings *set);
static void ssl_server_context_deinit(struct ssl_server_context **_ctx);
static unsigned int ssl_server_context_hash(const void *p)
{
const struct ssl_server_context *ctx = p;
unsigned int i, g, h = 0;
for (i = 0; i < 16 && ctx->cert[i] != '\0'; i++) {
h = (h << 4) + ctx->cert[i];
if ((g = h & 0xf0000000UL)) {
h = h ^ (g >> 24);
h = h ^ g;
}
}
return h;
}
static int ssl_server_context_cmp(const void *p1, const void *p2)
{
const struct ssl_server_context *ctx1 = p1, *ctx2 = p2;
if (strcmp(ctx1->cert, ctx2->cert) != 0)
return 1;
if (strcmp(ctx1->key, ctx2->key) != 0)
return 1;
if (null_strcmp(ctx1->cipher_list, ctx2->cipher_list) != 0)
return 1;
return ctx1->verify_client_cert == ctx2->verify_client_cert ? 0 : 1;
}
#ifdef APPLE_OS_X_SERVER
static void apple_password_input(struct apple_password_ctx *ctx)
{
int ret;
while ((ret = i_stream_read(ctx->from_certadmin)) > 0) {
ctx->reply = i_stream_next_line(ctx->from_certadmin);
if (ctx->reply != NULL)
break;
}
if (ret == -2)
ctx->overflow = TRUE;
if (ret < 0)
io_loop_stop(ctx->ioloop);
}
static void apple_password_timeout(struct apple_password_ctx *ctx)
{
ctx->timed_out = TRUE;
io_loop_stop(ctx->ioloop);
}
static int apple_password_callback (char *in_buf, int in_size, int in_rwflag ATTR_UNUSED, void *in_user_data)
{
CallbackUserData *cb_data = (CallbackUserData *) in_user_data;
int child_to_parent[2], fd, status;
pid_t pid;
char *args[4];
struct apple_password_ctx *ctx;
struct io *io;
struct timeout *timeout;
char *certadmin = "/usr/sbin/certadmin";
if (cb_data == NULL || strlen(cb_data->key) == 0 ||
cb_data->len >= FILENAME_MAX || cb_data->len == 0 ||
in_buf == NULL) {
i_error("apple_password_callback: invalid arguments");
return 0;
}
if (pipe(child_to_parent) < 0) {
i_error("apple_password_callback: pipe() failed: %m");
return 0;
}
pid = fork();
if (pid < 0) {
i_error("apple_password_callback: fork() failed: %m");
close(child_to_parent[0]);
close(child_to_parent[1]);
return 0;
}
if (pid == 0) {
close(child_to_parent[0]);
close(0);
close(1);
dup2(child_to_parent[1], 1);
for (fd = getdtablesize(); --fd >= 3; )
(void) close(fd);
i_set_fatal_handler(NULL);
i_set_error_handler(NULL);
i_set_info_handler(NULL);
args[0] = certadmin;
args[1] = "--get-private-key-passphrase";
args[2] = cb_data->key;
args[3] = NULL;
execv(certadmin, args);
i_fatal_status(FATAL_EXEC,
"apple_password_callback: execv(%s) failed: %m",
certadmin);
}
close(child_to_parent[1]);
fd_set_nonblock(child_to_parent[0], TRUE);
ctx = t_new(struct apple_password_ctx, 1);
ctx->from_certadmin = i_stream_create_fd(child_to_parent[0], 128, TRUE);
ctx->ioloop = io_loop_create();
io = io_add(child_to_parent[0], IO_READ, apple_password_input, ctx);
timeout = timeout_add(10 * 1000, apple_password_timeout, ctx);
io_loop_run(ctx->ioloop);
timeout_remove(&timeout);
io_remove(&io);
io_loop_destroy(&ctx->ioloop);
if (ctx->timed_out) {
kill(pid, SIGKILL);
i_error("apple_password_callback: timed out waiting for %s",
certadmin);
waitpid(pid, NULL, 0);
} else if (waitpid(pid, &status, 0) < 0 ||
!WIFEXITED(status) || WEXITSTATUS(status) != 0)
i_error("apple_password_callback: %s terminated abnormally",
certadmin);
else if (ctx->reply == NULL || *ctx->reply == '\0')
i_error("apple_password_callback: %s output from %s",
ctx->overflow ? "too much" : "no", certadmin);
else {
strlcpy(in_buf, ctx->reply, in_size);
}
i_stream_destroy(&ctx->from_certadmin);
return strlen(in_buf);
}
#endif
static void ssl_params_corrupted(void)
{
i_fatal("Corrupted SSL parameters file: "
PKG_STATEDIR"/ssl-parameters.dat");
}
static void read_next(struct ssl_parameters *params, void *data, size_t size)
{
int ret;
if ((ret = read_full(params->fd, data, size)) < 0)
i_fatal("read(%s) failed: %m", params->path);
if (ret == 0)
ssl_params_corrupted();
}
static bool read_dh_parameters_next(struct ssl_parameters *params)
{
unsigned char *buf;
const unsigned char *cbuf;
unsigned int len;
int bits;
read_next(params, &bits, sizeof(bits));
if (bits == 0)
return FALSE;
read_next(params, &len, sizeof(len));
if (len > 1024*100)
ssl_params_corrupted();
buf = i_malloc(len);
read_next(params, buf, len);
cbuf = buf;
switch (bits) {
case 512:
params->dh_512 = d2i_DHparams(NULL, &cbuf, len);
break;
case 1024:
params->dh_1024 = d2i_DHparams(NULL, &cbuf, len);
break;
default:
ssl_params_corrupted();
}
i_free(buf);
return TRUE;
}
static void ssl_free_parameters(struct ssl_parameters *params)
{
if (params->dh_512 != NULL) {
DH_free(params->dh_512);
params->dh_512 = NULL;
}
if (params->dh_1024 != NULL) {
DH_free(params->dh_1024);
params->dh_1024 = NULL;
}
}
static void ssl_refresh_parameters(struct ssl_parameters *params)
{
char c;
int ret;
if (params->last_refresh > ioloop_time - SSL_PARAMFILE_CHECK_INTERVAL)
return;
params->last_refresh = ioloop_time;
params->fd = net_connect_unix(params->path);
if (params->fd == -1) {
i_error("connect(%s) failed: %m", params->path);
return;
}
net_set_nonblock(params->fd, FALSE);
ssl_free_parameters(params);
while (read_dh_parameters_next(params)) ;
if ((ret = read_full(params->fd, &c, 1)) < 0)
i_fatal("read(%s) failed: %m", params->path);
else if (ret != 0) {
ssl_params_corrupted();
}
if (close(params->fd) < 0)
i_error("close(%s) failed: %m", params->path);
params->fd = -1;
}
static void ssl_set_io(struct ssl_proxy *proxy, enum ssl_io_action action)
{
switch (action) {
case SSL_ADD_INPUT:
if (proxy->io_ssl_read != NULL)
break;
proxy->io_ssl_read = io_add(proxy->fd_ssl, IO_READ,
ssl_step, proxy);
break;
case SSL_REMOVE_INPUT:
if (proxy->io_ssl_read != NULL)
io_remove(&proxy->io_ssl_read);
break;
case SSL_ADD_OUTPUT:
if (proxy->io_ssl_write != NULL)
break;
proxy->io_ssl_write = io_add(proxy->fd_ssl, IO_WRITE,
ssl_step, proxy);
break;
case SSL_REMOVE_OUTPUT:
if (proxy->io_ssl_write != NULL)
io_remove(&proxy->io_ssl_write);
break;
}
}
static void plain_block_input(struct ssl_proxy *proxy, bool block)
{
if (block) {
if (proxy->io_plain_read != NULL)
io_remove(&proxy->io_plain_read);
} else {
if (proxy->io_plain_read == NULL) {
proxy->io_plain_read = io_add(proxy->fd_plain, IO_READ,
plain_read, proxy);
}
}
}
static void plain_read(struct ssl_proxy *proxy)
{
ssize_t ret;
bool corked = FALSE;
if (proxy->sslout_size == sizeof(proxy->sslout_buf)) {
plain_block_input(proxy, TRUE);
return;
}
proxy->refcount++;
while (proxy->sslout_size < sizeof(proxy->sslout_buf) &&
!proxy->destroyed) {
ret = net_receive(proxy->fd_plain,
proxy->sslout_buf + proxy->sslout_size,
sizeof(proxy->sslout_buf) -
proxy->sslout_size);
if (ret <= 0) {
if (ret < 0)
ssl_proxy_destroy(proxy);
break;
} else {
proxy->sslout_size += ret;
if (!corked) {
net_set_cork(proxy->fd_ssl, TRUE);
corked = TRUE;
}
ssl_write(proxy);
}
}
if (corked)
net_set_cork(proxy->fd_ssl, FALSE);
ssl_proxy_unref(proxy);
}
static void plain_write(struct ssl_proxy *proxy)
{
ssize_t ret;
proxy->refcount++;
ret = net_transmit(proxy->fd_plain, proxy->plainout_buf,
proxy->plainout_size);
if (ret < 0)
ssl_proxy_destroy(proxy);
else {
proxy->plainout_size -= ret;
memmove(proxy->plainout_buf, proxy->plainout_buf + ret,
proxy->plainout_size);
if (proxy->plainout_size > 0) {
if (proxy->io_plain_write == NULL) {
proxy->io_plain_write =
io_add(proxy->fd_plain, IO_WRITE,
plain_write, proxy);
}
} else {
if (proxy->io_plain_write != NULL)
io_remove(&proxy->io_plain_write);
}
ssl_set_io(proxy, SSL_ADD_INPUT);
if (SSL_pending(proxy->ssl) > 0)
ssl_read(proxy);
}
ssl_proxy_unref(proxy);
}
static const char *ssl_err2str(unsigned long err, const char *data, int flags)
{
const char *ret;
char *buf;
size_t err_size = 256;
buf = t_malloc(err_size);
buf[err_size-1] = '\0';
ERR_error_string_n(err, buf, err_size-1);
ret = buf;
if ((flags & ERR_TXT_STRING) != 0)
ret = t_strdup_printf("%s: %s", buf, data);
return ret;
}
static const char *ssl_last_error(void)
{
unsigned long err;
const char *data;
int flags;
err = ERR_get_error_line_data(NULL, NULL, &data, &flags);
while (err != 0 && ERR_peek_error() != 0) {
i_error("SSL: Stacked error: %s",
ssl_err2str(err, data, flags));
err = ERR_get_error();
}
if (err == 0) {
if (errno != 0)
return strerror(errno);
return "Unknown error";
}
return ssl_err2str(err, data, flags);
}
static void ssl_handle_error(struct ssl_proxy *proxy, int ret,
const char *func_name)
{
const char *errstr = NULL;
int err;
proxy->refcount++;
i_free_and_null(proxy->last_error);
err = SSL_get_error(proxy->ssl, ret);
switch (err) {
case SSL_ERROR_WANT_READ:
ssl_set_io(proxy, SSL_ADD_INPUT);
break;
case SSL_ERROR_WANT_WRITE:
ssl_set_io(proxy, SSL_ADD_OUTPUT);
break;
case SSL_ERROR_SYSCALL:
if (ERR_peek_error() != 0)
errstr = ssl_last_error();
else if (ret != 0)
errstr = strerror(errno);
else {
errstr = "Disconnected";
break;
}
errstr = t_strdup_printf("%s syscall failed: %s",
func_name, errstr);
break;
case SSL_ERROR_ZERO_RETURN:
ssl_proxy_destroy(proxy);
break;
case SSL_ERROR_SSL:
if (ERR_GET_REASON(ERR_peek_error()) == ERR_R_MALLOC_FAILURE) {
i_error("OpenSSL malloc() failed. "
"You may need to increase login_process_size");
}
errstr = t_strdup_printf("%s failed: %s",
func_name, ssl_last_error());
break;
default:
errstr = t_strdup_printf("%s failed: unknown failure %d (%s)",
func_name, err, ssl_last_error());
break;
}
if (errstr != NULL) {
proxy->last_error = i_strdup(errstr);
ssl_proxy_destroy(proxy);
}
ssl_proxy_unref(proxy);
}
static void ssl_handshake(struct ssl_proxy *proxy)
{
int ret;
if (proxy->client_proxy) {
ret = SSL_connect(proxy->ssl);
if (ret != 1) {
ssl_handle_error(proxy, ret, "SSL_connect()");
return;
}
} else {
ret = SSL_accept(proxy->ssl);
if (ret != 1) {
ssl_handle_error(proxy, ret, "SSL_accept()");
return;
}
}
i_free_and_null(proxy->last_error);
proxy->handshaked = TRUE;
ssl_set_io(proxy, SSL_ADD_INPUT);
plain_block_input(proxy, FALSE);
if (proxy->handshake_callback != NULL) {
if (proxy->handshake_callback(proxy->handshake_context) < 0)
ssl_proxy_destroy(proxy);
}
}
static void ssl_read(struct ssl_proxy *proxy)
{
int ret;
while (proxy->plainout_size < sizeof(proxy->plainout_buf) &&
!proxy->destroyed) {
ret = SSL_read(proxy->ssl,
proxy->plainout_buf + proxy->plainout_size,
sizeof(proxy->plainout_buf) -
proxy->plainout_size);
if (ret <= 0) {
ssl_handle_error(proxy, ret, "SSL_read()");
break;
} else {
i_free_and_null(proxy->last_error);
proxy->plainout_size += ret;
plain_write(proxy);
}
}
}
static void ssl_write(struct ssl_proxy *proxy)
{
int ret;
ret = SSL_write(proxy->ssl, proxy->sslout_buf, proxy->sslout_size);
if (ret <= 0)
ssl_handle_error(proxy, ret, "SSL_write()");
else {
i_free_and_null(proxy->last_error);
proxy->sslout_size -= ret;
memmove(proxy->sslout_buf, proxy->sslout_buf + ret,
proxy->sslout_size);
ssl_set_io(proxy, proxy->sslout_size > 0 ?
SSL_ADD_OUTPUT : SSL_REMOVE_OUTPUT);
plain_block_input(proxy, FALSE);
}
}
static void ssl_step(struct ssl_proxy *proxy)
{
proxy->refcount++;
if (!proxy->handshaked)
ssl_handshake(proxy);
if (proxy->handshaked) {
if (proxy->plainout_size == sizeof(proxy->plainout_buf))
ssl_set_io(proxy, SSL_REMOVE_INPUT);
else
ssl_read(proxy);
if (proxy->sslout_size == 0)
ssl_set_io(proxy, SSL_REMOVE_OUTPUT);
else {
net_set_cork(proxy->fd_ssl, TRUE);
ssl_write(proxy);
net_set_cork(proxy->fd_ssl, FALSE);
}
}
ssl_proxy_unref(proxy);
}
static int
ssl_proxy_alloc_common(SSL_CTX *ssl_ctx, int fd, const struct ip_addr *ip,
const struct login_settings *set,
struct ssl_proxy **proxy_r)
{
struct ssl_proxy *proxy;
SSL *ssl;
int sfd[2];
i_assert(fd != -1);
*proxy_r = NULL;
if (!ssl_initialized) {
i_error("SSL support not enabled in configuration");
return -1;
}
ssl_refresh_parameters(&ssl_params);
ssl = SSL_new(ssl_ctx);
if (ssl == NULL) {
i_error("SSL_new() failed: %s", ssl_last_error());
return -1;
}
if (SSL_set_fd(ssl, fd) != 1) {
i_error("SSL_set_fd() failed: %s", ssl_last_error());
SSL_free(ssl);
return -1;
}
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sfd) < 0) {
i_error("socketpair() failed: %m");
SSL_free(ssl);
return -1;
}
net_set_nonblock(sfd[0], TRUE);
net_set_nonblock(sfd[1], TRUE);
net_set_nonblock(fd, TRUE);
proxy = i_new(struct ssl_proxy, 1);
proxy->refcount = 2;
proxy->ssl = ssl;
proxy->set = set;
proxy->fd_ssl = fd;
proxy->fd_plain = sfd[0];
proxy->ip = *ip;
SSL_set_ex_data(ssl, extdata_index, proxy);
ssl_proxy_count++;
DLLIST_PREPEND(&ssl_proxies, proxy);
*proxy_r = proxy;
return sfd[1];
}
static struct ssl_server_context *
ssl_server_context_get(const struct login_settings *set)
{
struct ssl_server_context *ctx, lookup_ctx;
memset(&lookup_ctx, 0, sizeof(lookup_ctx));
lookup_ctx.cert = set->ssl_cert;
lookup_ctx.key = set->ssl_key;
lookup_ctx.ca = set->ssl_ca;
lookup_ctx.cipher_list = set->ssl_cipher_list;
lookup_ctx.verify_client_cert = set->ssl_verify_client_cert;
ctx = hash_table_lookup(ssl_servers, &lookup_ctx);
if (ctx == NULL)
ctx = ssl_server_context_init(set);
return ctx;
}
int ssl_proxy_alloc(int fd, const struct ip_addr *ip,
const struct login_settings *set,
struct ssl_proxy **proxy_r)
{
struct ssl_server_context *ctx;
ctx = ssl_server_context_get(set);
return ssl_proxy_alloc_common(ctx->ctx, fd, ip, set, proxy_r);
}
int ssl_proxy_client_alloc(int fd, struct ip_addr *ip,
const struct login_settings *set,
ssl_handshake_callback_t *callback, void *context,
struct ssl_proxy **proxy_r)
{
int ret;
ret = ssl_proxy_alloc_common(ssl_client_ctx, fd, ip, set, proxy_r);
if (ret < 0)
return -1;
(*proxy_r)->handshake_callback = callback;
(*proxy_r)->handshake_context = context;
(*proxy_r)->client_proxy = TRUE;
return ret;
}
void ssl_proxy_start(struct ssl_proxy *proxy)
{
ssl_step(proxy);
}
void ssl_proxy_set_client(struct ssl_proxy *proxy, struct client *client)
{
i_assert(proxy->client == NULL);
client_ref(client);
proxy->client = client;
}
bool ssl_proxy_has_valid_client_cert(const struct ssl_proxy *proxy)
{
return proxy->cert_received && !proxy->cert_broken;
}
bool ssl_proxy_has_broken_client_cert(struct ssl_proxy *proxy)
{
return proxy->cert_received && proxy->cert_broken;
}
static const char *asn1_string_to_c(ASN1_STRING *asn_str)
{
const char *cstr;
unsigned int len;
len = ASN1_STRING_length(asn_str);
cstr = t_strndup(ASN1_STRING_data(asn_str), len);
if (strlen(cstr) != len) {
return "";
}
return cstr;
}
static const char *get_general_dns_name(const GENERAL_NAME *name)
{
if (ASN1_STRING_type(name->d.ia5) != V_ASN1_IA5STRING)
return "";
return asn1_string_to_c(name->d.ia5);
}
static const char *get_cname(X509 *cert)
{
X509_NAME *name;
X509_NAME_ENTRY *entry;
ASN1_STRING *str;
int cn_idx;
name = X509_get_subject_name(cert);
if (name == NULL)
return "";
cn_idx = X509_NAME_get_index_by_NID(name, NID_commonName, -1);
if (cn_idx == -1)
return "";
entry = X509_NAME_get_entry(name, cn_idx);
i_assert(entry != NULL);
str = X509_NAME_ENTRY_get_data(entry);
i_assert(str != NULL);
return asn1_string_to_c(str);
}
static int openssl_cert_match_name(SSL *ssl, const char *verify_name)
{
X509 *cert;
STACK_OF(GENERAL_NAME) *gnames;
const GENERAL_NAME *gn;
const char *dnsname;
bool dns_names = FALSE;
unsigned int i, count;
cert = SSL_get_peer_certificate(ssl);
i_assert(cert != NULL);
gnames = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
count = gnames == NULL ? 0 : sk_GENERAL_NAME_num(gnames);
for (i = 0; i < count; i++) {
gn = sk_GENERAL_NAME_value(gnames, i);
if (gn->type == GEN_DNS) {
dns_names = TRUE;
dnsname = get_general_dns_name(gn);
if (strcmp(dnsname, verify_name) == 0)
break;
}
}
sk_GENERAL_NAME_pop_free(gnames, GENERAL_NAME_free);
if (dns_names)
return i < count ? 0 : -1;
return strcmp(get_cname(cert), verify_name) == 0 ? 0 : -1;
}
int ssl_proxy_cert_match_name(struct ssl_proxy *proxy, const char *verify_name)
{
return openssl_cert_match_name(proxy->ssl, verify_name);
}
const char *ssl_proxy_get_peer_name(struct ssl_proxy *proxy)
{
X509 *x509;
char *name;
int len;
if (!ssl_proxy_has_valid_client_cert(proxy))
return NULL;
x509 = SSL_get_peer_certificate(proxy->ssl);
if (x509 == NULL)
return NULL;
len = X509_NAME_get_text_by_NID(X509_get_subject_name(x509),
ssl_username_nid, NULL, 0);
if (len < 0)
name = "";
else {
name = t_malloc(len + 1);
if (X509_NAME_get_text_by_NID(X509_get_subject_name(x509),
ssl_username_nid, name, len + 1) < 0)
name = "";
else if (strlen(name) != (size_t)len) {
name = "";
}
}
X509_free(x509);
return *name == '\0' ? NULL : name;
}
bool ssl_proxy_is_handshaked(const struct ssl_proxy *proxy)
{
return proxy->handshaked;
}
const char *ssl_proxy_get_last_error(const struct ssl_proxy *proxy)
{
return proxy->last_error;
}
const char *ssl_proxy_get_security_string(struct ssl_proxy *proxy)
{
const SSL_CIPHER *cipher;
int bits, alg_bits;
const char *comp_str;
if (!proxy->handshaked)
return "";
cipher = SSL_get_current_cipher(proxy->ssl);
bits = SSL_CIPHER_get_bits(cipher, &alg_bits);
comp_str = ssl_proxy_get_compression(proxy);
comp_str = comp_str == NULL ? "" : t_strconcat(" ", comp_str, NULL);
return t_strdup_printf("%s with cipher %s (%d/%d bits)%s",
SSL_get_version(proxy->ssl),
SSL_CIPHER_get_name(cipher),
bits, alg_bits, comp_str);
}
const char *ssl_proxy_get_compression(struct ssl_proxy *proxy ATTR_UNUSED)
{
#ifdef HAVE_SSL_COMPRESSION
const COMP_METHOD *comp;
comp = SSL_get_current_compression(proxy->ssl);
return comp == NULL ? NULL : SSL_COMP_get_name(comp);
#else
return NULL;
#endif
}
void ssl_proxy_free(struct ssl_proxy **_proxy)
{
struct ssl_proxy *proxy = *_proxy;
*_proxy = NULL;
ssl_proxy_unref(proxy);
}
static void ssl_proxy_unref(struct ssl_proxy *proxy)
{
if (--proxy->refcount > 0)
return;
i_assert(proxy->refcount == 0);
SSL_free(proxy->ssl);
if (proxy->last_error != NULL)
i_free(proxy->last_error);
if (proxy->client != NULL)
client_unref(&proxy->client);
i_free(proxy);
}
static void ssl_proxy_destroy(struct ssl_proxy *proxy)
{
if (proxy->destroyed)
return;
proxy->destroyed = TRUE;
ssl_proxy_count--;
DLLIST_REMOVE(&ssl_proxies, proxy);
if (proxy->io_ssl_read != NULL)
io_remove(&proxy->io_ssl_read);
if (proxy->io_ssl_write != NULL)
io_remove(&proxy->io_ssl_write);
if (proxy->io_plain_read != NULL)
io_remove(&proxy->io_plain_read);
if (proxy->io_plain_write != NULL)
io_remove(&proxy->io_plain_write);
(void)SSL_shutdown(proxy->ssl);
(void)net_disconnect(proxy->fd_ssl);
(void)net_disconnect(proxy->fd_plain);
ssl_proxy_unref(proxy);
}
static RSA *ssl_gen_rsa_key(SSL *ssl ATTR_UNUSED,
int is_export ATTR_UNUSED, int keylength)
{
return RSA_generate_key(keylength, RSA_F4, NULL, NULL);
}
static DH *ssl_tmp_dh_callback(SSL *ssl ATTR_UNUSED,
int is_export, int keylength)
{
if (is_export && keylength == 512 && ssl_params.dh_512 != NULL)
return ssl_params.dh_512;
return ssl_params.dh_1024;
}
static void ssl_info_callback(const SSL *ssl, int where, int ret)
{
struct ssl_proxy *proxy;
proxy = SSL_get_ex_data(ssl, extdata_index);
if (!proxy->set->verbose_ssl)
return;
if ((where & SSL_CB_ALERT) != 0) {
i_warning("SSL alert: where=0x%x, ret=%d: %s %s [%s]",
where, ret, SSL_alert_type_string_long(ret),
SSL_alert_desc_string_long(ret),
net_ip2addr(&proxy->ip));
} else if (ret == 0) {
i_warning("SSL failed: where=0x%x: %s [%s]",
where, SSL_state_string_long(ssl),
net_ip2addr(&proxy->ip));
} else {
i_warning("SSL: where=0x%x, ret=%d: %s [%s]",
where, ret, SSL_state_string_long(ssl),
net_ip2addr(&proxy->ip));
}
}
static int ssl_verify_client_cert(int preverify_ok, X509_STORE_CTX *ctx)
{
SSL *ssl;
struct ssl_proxy *proxy;
ssl = X509_STORE_CTX_get_ex_data(ctx,
SSL_get_ex_data_X509_STORE_CTX_idx());
proxy = SSL_get_ex_data(ssl, extdata_index);
proxy->cert_received = TRUE;
if (proxy->set->verbose_ssl ||
(proxy->set->verbose_auth && !preverify_ok)) {
char buf[1024];
X509_NAME *subject;
subject = X509_get_subject_name(ctx->current_cert);
(void)X509_NAME_oneline(subject, buf, sizeof(buf));
buf[sizeof(buf)-1] = '\0';
if (!preverify_ok)
i_info("Invalid certificate: %s: %s", X509_verify_cert_error_string(ctx->error),buf);
else
i_info("Valid certificate: %s", buf);
}
if (ctx->error == X509_V_ERR_UNABLE_TO_GET_CRL && proxy->client_proxy) {
preverify_ok = 1;
}
if (!preverify_ok)
proxy->cert_broken = TRUE;
return 1;
}
static int
pem_password_callback(char *buf, int size, int rwflag ATTR_UNUSED,
void *userdata)
{
if (userdata == NULL) {
i_error("SSL private key file is password protected, "
"but password isn't given");
return 0;
}
if (i_strocpy(buf, userdata, size) < 0)
return 0;
return strlen(buf);
}
unsigned int ssl_proxy_get_count(void)
{
return ssl_proxy_count;
}
static bool is_pem_key(const char *cert)
{
return strstr(cert, "PRIVATE KEY---") != NULL;
}
static STACK_OF(X509_NAME) *load_ca(X509_STORE *store, const char *ca)
{
STACK_OF(X509_INFO) *inf;
STACK_OF(X509_NAME) *xnames;
X509_INFO *itmp;
X509_NAME *xname;
BIO *bio;
int i;
bio = BIO_new_mem_buf(t_strdup_noconst(ca), strlen(ca));
if (bio == NULL)
i_fatal("BIO_new_mem_buf() failed");
inf = PEM_X509_INFO_read_bio(bio, NULL, NULL, NULL);
if (inf == NULL)
i_fatal("Couldn't parse ssl_ca: %s", ssl_last_error());
BIO_free(bio);
xnames = sk_X509_NAME_new_null();
if (xnames == NULL)
i_fatal("sk_X509_NAME_new_null() failed");
for(i = 0; i < sk_X509_INFO_num(inf); i++) {
itmp = sk_X509_INFO_value(inf, i);
if(itmp->x509) {
X509_STORE_add_cert(store, itmp->x509);
xname = X509_get_subject_name(itmp->x509);
if (xname != NULL)
xname = X509_NAME_dup(xname);
if (xname != NULL)
sk_X509_NAME_push(xnames, xname);
}
if(itmp->crl)
X509_STORE_add_crl(store, itmp->crl);
}
sk_X509_INFO_pop_free(inf, X509_INFO_free);
return xnames;
}
static STACK_OF(X509_NAME) *
ssl_proxy_ctx_init(SSL_CTX *ssl_ctx, const struct login_settings *set)
{
X509_STORE *store;
STACK_OF(X509_NAME) *xnames = NULL;
SSL_CTX_set_options(ssl_ctx, (SSL_OP_ALL &
~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) | SSL_OP_NO_SSLv2);
#ifdef SSL_MODE_RELEASE_BUFFERS
SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
#endif
if (*set->ssl_ca != '\0') {
store = SSL_CTX_get_cert_store(ssl_ctx);
xnames = load_ca(store, set->ssl_ca);
}
SSL_CTX_set_info_callback(ssl_ctx, ssl_info_callback);
if (SSL_CTX_need_tmp_RSA(ssl_ctx))
SSL_CTX_set_tmp_rsa_callback(ssl_ctx, ssl_gen_rsa_key);
SSL_CTX_set_tmp_dh_callback(ssl_ctx, ssl_tmp_dh_callback);
return xnames;
}
static void
ssl_proxy_ctx_verify_client(SSL_CTX *ssl_ctx, STACK_OF(X509_NAME) *ca_names)
{
#if OPENSSL_VERSION_NUMBER >= 0x00907000L
X509_STORE *store;
store = SSL_CTX_get_cert_store(ssl_ctx);
X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK |
X509_V_FLAG_CRL_CHECK_ALL);
#endif
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE,
ssl_verify_client_cert);
SSL_CTX_set_client_CA_list(ssl_ctx, ca_names);
}
static const char *ssl_proxy_get_use_certificate_error(const char *cert)
{
unsigned long err;
err = ERR_peek_error();
if (ERR_GET_LIB(err) != ERR_LIB_PEM ||
ERR_GET_REASON(err) != PEM_R_NO_START_LINE)
return ssl_last_error();
else if (is_pem_key(cert)) {
return "The file contains a private key "
"(you've mixed ssl_cert and ssl_key settings)";
} else if (strchr(cert, '\n') == NULL) {
return t_strdup_printf("There is no valid PEM certificate. "
"(You probably forgot '<' from ssl_cert=<%s)", cert);
} else {
return "There is no valid PEM certificate.";
}
}
static EVP_PKEY *ssl_proxy_load_key(const struct login_settings *set)
{
EVP_PKEY *pkey;
BIO *bio;
const char *password;
char *dup_password;
bio = BIO_new_mem_buf(t_strdup_noconst(set->ssl_key),
strlen(set->ssl_key));
if (bio == NULL)
i_fatal("BIO_new_mem_buf() failed");
password = *set->ssl_key_password != '\0' ? set->ssl_key_password :
getenv(MASTER_SSL_KEY_PASSWORD_ENV);
dup_password = t_strdup_noconst(password);
#ifdef APPLE_OS_X_SERVER
pkey = NULL;
if ( strlen( set->ssl_key_path ) < FILENAME_MAX )
{
if ( s_user_data == NULL )
{
s_user_data = malloc( sizeof(CallbackUserData) );
if ( s_user_data != NULL )
{
memset( s_user_data, 0, sizeof(CallbackUserData) );
}
}
if ( s_user_data != NULL )
{
i_snprintf( s_user_data->key, FILENAME_MAX, "%s", set->ssl_key_path );
s_user_data->len = strlen( s_user_data->key );
pkey = PEM_read_bio_PrivateKey(bio, NULL,
apple_password_callback, s_user_data);
}
else
{
i_info( "Could not set custom callback for: %s", set->ssl_key_path );
}
}
else
{
i_info( "Key file path too big for custom callback for: %s", set->ssl_key_path );
}
if (pkey == NULL)
#endif
pkey = PEM_read_bio_PrivateKey(bio, NULL, pem_password_callback,
dup_password);
if (pkey == NULL)
i_fatal("Couldn't parse private ssl_key");
BIO_free(bio);
return pkey;
}
static const char *ssl_key_load_error(void)
{
unsigned long err = ERR_peek_error();
if (ERR_GET_LIB(err) == ERR_LIB_X509 &&
ERR_GET_REASON(err) == X509_R_KEY_VALUES_MISMATCH)
return "Key is for a different cert than ssl_cert";
else
return ssl_last_error();
}
static void ssl_proxy_ctx_use_key(SSL_CTX *ctx, const struct login_settings *set)
{
EVP_PKEY *pkey;
pkey = ssl_proxy_load_key(set);
if (SSL_CTX_use_PrivateKey(ctx, pkey) != 1)
i_fatal("Can't load private ssl_key: %s", ssl_key_load_error());
EVP_PKEY_free(pkey);
}
static int
ssl_proxy_ctx_use_certificate_chain(SSL_CTX *ctx, const char *cert)
{
BIO *in;
X509 *x;
int ret = 0;
in = BIO_new_mem_buf(t_strdup_noconst(cert), strlen(cert));
if (in == NULL)
i_fatal("BIO_new_mem_buf() failed");
x = PEM_read_bio_X509(in, NULL, NULL, NULL);
if (x == NULL)
goto end;
ret = SSL_CTX_use_certificate(ctx, x);
#if 0
if (ERR_peek_error() != 0)
ret = 0;
#endif
if (ret != 0) {
X509 *ca;
int r;
unsigned long err;
while ((ca = PEM_read_bio_X509(in,NULL,NULL,NULL)) != NULL) {
r = SSL_CTX_add_extra_chain_cert(ctx, ca);
if (!r) {
X509_free(ca);
ret = 0;
goto end;
}
}
err = ERR_peek_last_error();
if (ERR_GET_LIB(err) == ERR_LIB_PEM && ERR_GET_REASON(err) == PEM_R_NO_START_LINE)
ERR_clear_error();
else
ret = 0;
}
end:
if (x != NULL) X509_free(x);
BIO_free(in);
return ret;
}
#ifdef HAVE_SSL_GET_SERVERNAME
static void ssl_servername_callback(SSL *ssl, int *al ATTR_UNUSED,
void *context ATTR_UNUSED)
{
struct ssl_server_context *ctx;
struct ssl_proxy *proxy;
struct client *client;
const char *host;
void **other_sets;
proxy = SSL_get_ex_data(ssl, extdata_index);
host = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
client = proxy->client;
if (!client->ssl_servername_settings_read) {
client->ssl_servername_settings_read = TRUE;
client->set = login_settings_read(client->pool,
&client->local_ip,
&client->ip, host,
&other_sets);
}
ctx = ssl_server_context_get(client->set);
SSL_set_SSL_CTX(ssl, ctx->ctx);
}
#endif
static struct ssl_server_context *
ssl_server_context_init(const struct login_settings *set)
{
struct ssl_server_context *ctx;
SSL_CTX *ssl_ctx;
pool_t pool;
STACK_OF(X509_NAME) *xnames;
pool = pool_alloconly_create("ssl server context", 4096);
ctx = p_new(pool, struct ssl_server_context, 1);
ctx->pool = pool;
ctx->cert = p_strdup(pool, set->ssl_cert);
ctx->key = p_strdup(pool, set->ssl_key);
ctx->ca = p_strdup(pool, set->ssl_ca);
ctx->cipher_list = p_strdup(pool, set->ssl_cipher_list);
ctx->verify_client_cert = set->ssl_verify_client_cert;
ctx->ctx = ssl_ctx = SSL_CTX_new(SSLv23_server_method());
if (ssl_ctx == NULL)
i_fatal("SSL_CTX_new() failed");
xnames = ssl_proxy_ctx_init(ssl_ctx, set);
if (SSL_CTX_set_cipher_list(ssl_ctx, ctx->cipher_list) != 1) {
i_fatal("Can't set cipher list to '%s': %s",
ctx->cipher_list, ssl_last_error());
}
if (ssl_proxy_ctx_use_certificate_chain(ctx->ctx, ctx->cert) != 1) {
i_fatal("Can't load ssl_cert: %s",
ssl_proxy_get_use_certificate_error(ctx->cert));
}
#ifdef HAVE_SSL_GET_SERVERNAME
if (SSL_CTX_set_tlsext_servername_callback(ctx->ctx,
ssl_servername_callback) != 1) {
if (set->verbose_ssl)
i_debug("OpenSSL library doesn't support SNI");
}
#endif
ssl_proxy_ctx_use_key(ctx->ctx, set);
SSL_CTX_set_info_callback(ctx->ctx, ssl_info_callback);
if (ctx->verify_client_cert)
ssl_proxy_ctx_verify_client(ctx->ctx, xnames);
hash_table_insert(ssl_servers, ctx, ctx);
return ctx;
}
static void ssl_server_context_deinit(struct ssl_server_context **_ctx)
{
struct ssl_server_context *ctx = *_ctx;
SSL_CTX_free(ctx->ctx);
pool_unref(&ctx->pool);
}
static void ssl_proxy_init_client(const struct login_settings *set)
{
STACK_OF(X509_NAME) *xnames;
if ((ssl_client_ctx = SSL_CTX_new(SSLv23_client_method())) == NULL)
i_fatal("SSL_CTX_new() failed");
xnames = ssl_proxy_ctx_init(ssl_client_ctx, set);
ssl_proxy_ctx_verify_client(ssl_client_ctx, xnames);
}
void ssl_proxy_init(void)
{
const struct login_settings *set = global_login_settings;
static char dovecot[] = "dovecot";
unsigned char buf;
if (strcmp(set->ssl, "no") == 0)
return;
SSL_library_init();
SSL_load_error_strings();
OpenSSL_add_all_algorithms();
extdata_index = SSL_get_ex_new_index(0, dovecot, NULL, NULL, NULL);
ssl_servers = hash_table_create(default_pool, default_pool, 0,
ssl_server_context_hash,
ssl_server_context_cmp);
(void)ssl_server_context_init(set);
ssl_proxy_init_client(set);
ssl_username_nid = OBJ_txt2nid(set->ssl_cert_username_field);
if (ssl_username_nid == NID_undef) {
i_fatal("Invalid ssl_cert_username_field: %s",
set->ssl_cert_username_field);
}
(void)RAND_bytes(&buf, 1);
memset(&ssl_params, 0, sizeof(ssl_params));
ssl_params.path = SSL_PARAMETERS_PATH;
ssl_proxy_count = 0;
ssl_proxies = NULL;
ssl_initialized = TRUE;
}
void ssl_proxy_deinit(void)
{
struct hash_iterate_context *iter;
void *key, *value;
if (!ssl_initialized)
return;
while (ssl_proxies != NULL)
ssl_proxy_destroy(ssl_proxies);
iter = hash_table_iterate_init(ssl_servers);
while (hash_table_iterate(iter, &key, &value)) {
struct ssl_server_context *ctx = value;
ssl_server_context_deinit(&ctx);
}
hash_table_iterate_deinit(&iter);
hash_table_destroy(&ssl_servers);
ssl_free_parameters(&ssl_params);
SSL_CTX_free(ssl_client_ctx);
EVP_cleanup();
ERR_free_strings();
}
#endif