Mercurial > dovecot > core-2.2
changeset 12616:bd23d4e10fa1
Added lib-ssl-iostream for handling SSL connections more easily.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Mon, 31 Jan 2011 18:40:27 +0200 |
parents | 3dde816d945d |
children | 7b7434fef6ff |
files | configure.in src/Makefile.am src/lib-ssl-iostream/Makefile.am src/lib-ssl-iostream/iostream-openssl-context.c src/lib-ssl-iostream/iostream-openssl-params.c src/lib-ssl-iostream/iostream-openssl.c src/lib-ssl-iostream/iostream-openssl.h src/lib-ssl-iostream/iostream-ssl.h src/lib-ssl-iostream/istream-openssl.c src/lib-ssl-iostream/ostream-openssl.c |
diffstat | 10 files changed, 1470 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- a/configure.in Mon Jan 31 04:02:04 2011 +0200 +++ b/configure.in Mon Jan 31 18:40:27 2011 +0200 @@ -2664,6 +2664,7 @@ src/lib-otp/Makefile src/lib-dovecot/Makefile src/lib-settings/Makefile +src/lib-ssl-iostream/Makefile src/lib-test/Makefile src/lib-storage/Makefile src/lib-storage/list/Makefile
--- a/src/Makefile.am Mon Jan 31 04:02:04 2011 +0200 +++ b/src/Makefile.am Mon Jan 31 18:40:27 2011 +0200 @@ -17,6 +17,7 @@ lib-index \ lib-storage \ lib-sql \ + lib-ssl-iostream \ lib-ntlm \ lib-otp \ lib-lda \
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-ssl-iostream/Makefile.am Mon Jan 31 18:40:27 2011 +0200 @@ -0,0 +1,28 @@ +noinst_LTLIBRARIES = libssl_iostream.la + +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-test + +libssl_iostream_la_SOURCES = \ + iostream-openssl.c \ + iostream-openssl-context.c \ + iostream-openssl-params.c \ + istream-openssl.c \ + ostream-openssl.c + +libssl_iostream_la_LIBADD = \ + $(SSL_LIBS) + +headers = \ + iostream-openssl.h \ + iostream-ssl.h + +pkginc_libdir=$(pkgincludedir) +pkginc_lib_HEADERS = $(headers) + +pkglib_LTLIBRARIES = libdovecot-ssl.la +libdovecot_ssl_la_SOURCES = +libdovecot_ssl_la_LIBADD = libssl_iostream.la +libdovecot_ssl_la_DEPENDENCIES = libssl_iostream.la +libdovecot_ssl_la_LDFLAGS = -export-dynamic
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-ssl-iostream/iostream-openssl-context.c Mon Jan 31 18:40:27 2011 +0200 @@ -0,0 +1,450 @@ +/* Copyright (c) 2009 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "safe-memset.h" +#include "iostream-openssl.h" + +#include <openssl/crypto.h> +#include <openssl/x509.h> +#include <openssl/pem.h> +#include <openssl/ssl.h> +#include <openssl/err.h> +#include <openssl/rand.h> + +struct ssl_iostream_password_context { + const char *password; + const char *key_source; +}; + +static bool ssl_global_initialized = FALSE; +int dovecot_ssl_extdata_index; + +static void ssl_iostream_init_global(void); + +const char *ssl_iostream_error(void) +{ + unsigned long err; + char *buf; + size_t err_size = 256; + + err = ERR_get_error(); + if (err == 0) { + if (errno != 0) + return strerror(errno); + return "Unknown error"; + } + if (ERR_GET_REASON(err) == ERR_R_MALLOC_FAILURE) + i_fatal_status(FATAL_OUTOFMEM, "OpenSSL malloc() failed"); + + buf = t_malloc(err_size); + buf[err_size-1] = '\0'; + ERR_error_string_n(err, buf, err_size-1); + return buf; +} + +const char *ssl_iostream_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_iostream_error(); +} + +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) +{ + struct ssl_iostream *ssl_io; + + ssl_io = SSL_get_ex_data(ssl, dovecot_ssl_extdata_index); + /* Well, I'm not exactly sure why the logic in here is this. + It's the same as in Postfix, so it can't be too wrong. */ + if (is_export && keylength == 512 && ssl_io->ctx->dh_512 != NULL) + return ssl_io->ctx->dh_512; + else + return ssl_io->ctx->dh_1024; +} + +static int +pem_password_callback(char *buf, int size, int rwflag ATTR_UNUSED, + void *userdata) +{ + struct ssl_iostream_password_context *ctx = userdata; + + if (ctx->password == NULL) { + i_error("%s: SSL private key file is password protected, " + "but password isn't given", ctx->key_source); + return 0; + } + + if (i_strocpy(buf, userdata, size) < 0) { + i_error("%s: SSL private key password is too long", + ctx->key_source); + return 0; + } + return strlen(buf); +} + +int ssl_iostream_load_key(const struct ssl_iostream_settings *set, + const char *key_source, EVP_PKEY **pkey_r) +{ + struct ssl_iostream_password_context ctx; + EVP_PKEY *pkey; + BIO *bio; + char *key; + + key = t_strdup_noconst(set->key); + bio = BIO_new_mem_buf(key, strlen(key)); + if (bio == NULL) { + i_error("BIO_new_mem_buf() failed: %s", ssl_iostream_error()); + safe_memset(key, 0, strlen(key)); + return -1; + } + + ctx.password = set->key_password; + ctx.key_source = key_source; + + pkey = PEM_read_bio_PrivateKey(bio, NULL, pem_password_callback, &ctx); + if (pkey == NULL) { + i_error("%s: Couldn't parse private SSL key: %s", + key_source, ssl_iostream_error()); + } + BIO_free(bio); + + safe_memset(key, 0, strlen(key)); + *pkey_r = pkey; + return pkey == NULL ? -1 : 0; +} + +static int +ssl_iostream_ctx_use_key(struct ssl_iostream_context *ctx, + const struct ssl_iostream_settings *set) +{ + EVP_PKEY *pkey; + int ret = 0; + + if (ssl_iostream_load_key(set, ctx->source, &pkey) < 0) + return -1; + if (!SSL_CTX_use_PrivateKey(ctx->ssl_ctx, pkey)) { + i_error("%s: Can't load SSL private key: %s", + ctx->source, ssl_iostream_key_load_error()); + ret = -1; + } + EVP_PKEY_free(pkey); + return ret; +} + +static bool is_pem_key(const char *cert) +{ + return strstr(cert, "PRIVATE KEY---") != NULL; +} + +const char *ssl_iostream_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_iostream_error(); + else if (is_pem_key(cert)) { + return "The file contains a private key " + "(you've mixed ssl_cert and ssl_key settings)"; + } else { + return "There is no certificate."; + } +} + +static int ssl_ctx_use_certificate_chain(SSL_CTX *ctx, const char *cert) +{ + /* mostly just copy&pasted from SSL_CTX_use_certificate_chain_file() */ + 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 (ERR_peek_error() != 0) + ret = 0; + + if (ret != 0) { + /* If we could set up our certificate, now proceed to + * the CA certificates. + */ + 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; + } + } + /* When the while loop ends, it's usually just EOF. */ + 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; /* some real error */ + } + +end: + if (x != NULL) X509_free(x); + BIO_free(in); + return ret; +} + +static int load_ca(X509_STORE *store, const char *ca, + STACK_OF(X509_NAME) **xnames_r) +{ + /* mostly just copy&pasted from X509_load_cert_crl_file() */ + 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); + BIO_free(bio); + + if (inf == NULL) + return -1; + + 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); + *xnames_r = xnames; + return 0; +} + +static int +ssl_iostream_ctx_verify_remote_cert(struct ssl_iostream_context *ctx, + STACK_OF(X509_NAME) *ca_names) +{ +#if OPENSSL_VERSION_NUMBER >= 0x00907000L + X509_STORE *store; + + store = SSL_CTX_get_cert_store(ctx->ssl_ctx); + X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK | + X509_V_FLAG_CRL_CHECK_ALL); +#endif + + SSL_CTX_set_client_CA_list(ctx->ssl_ctx, ca_names); + return 0; +} + +static struct ssl_iostream_settings * +ssl_iostream_settings_dup(pool_t pool, + const struct ssl_iostream_settings *old_set) +{ + struct ssl_iostream_settings *new_set; + + new_set = p_new(pool, struct ssl_iostream_settings, 1); + new_set->cipher_list = p_strdup(pool, old_set->cipher_list); + new_set->cert = p_strdup(pool, old_set->cert); + new_set->key = p_strdup(pool, old_set->key); + new_set->key_password = p_strdup(pool, old_set->key_password); + + new_set->verbose = old_set->verbose; + return new_set; +} + +static int +ssl_iostream_context_set(struct ssl_iostream_context *ctx, + const struct ssl_iostream_settings *set) +{ + X509_STORE *store; + STACK_OF(X509_NAME) *xnames = NULL; + + ctx->set = ssl_iostream_settings_dup(ctx->pool, set); + if (set->cipher_list != NULL && + !SSL_CTX_set_cipher_list(ctx->ssl_ctx, set->cipher_list)) { + i_error("%s: Can't set cipher list to '%s': %s", + ctx->source, set->cipher_list, + ssl_iostream_error()); + return -1; + } + + if (set->cert != NULL && + ssl_ctx_use_certificate_chain(ctx->ssl_ctx, set->cert) < 0) { + i_error("%s: Can't load SSL certificate: %s", ctx->source, + ssl_iostream_get_use_certificate_error(set->cert)); + } + if (set->key != NULL) { + if (ssl_iostream_ctx_use_key(ctx, set) < 0) + return -1; + } + + /* set trusted CA certs */ + if (!set->verify_remote_cert) { + /* no CA */ + } else if (set->ca != NULL) { + store = SSL_CTX_get_cert_store(ctx->ssl_ctx); + if (load_ca(store, set->ca, &xnames) < 0) { + i_error("%s: Couldn't parse ssl_ca: %s", ctx->source, + ssl_iostream_error()); + return -1; + } + if (ssl_iostream_ctx_verify_remote_cert(ctx, xnames) < 0) + return -1; + } else if (set->ca_dir != NULL) { + if (!SSL_CTX_load_verify_locations(ctx->ssl_ctx, NULL, + set->ca_dir)) { + i_error("%s: Can't load CA certs from directory %s: %s", + ctx->source, set->ca_dir, ssl_iostream_error()); + return -1; + } + } else { + i_error("%s: Can't verify remote certs without CA", + ctx->source); + return -1; + } + + if (set->cert_username_field != NULL) { + ctx->username_nid = OBJ_txt2nid(set->cert_username_field); + if (ctx->username_nid == NID_undef) { + i_error("%s: Invalid cert_username_field: %s", + ctx->source, set->cert_username_field); + } + } + return 0; +} + +static int +ssl_iostream_context_init_common(struct ssl_iostream_context *ctx, + const char *source, + const struct ssl_iostream_settings *set) +{ + ctx->pool = pool_alloconly_create("ssl iostream context", 4096); + ctx->source = p_strdup(ctx->pool, source); + + SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2); + if (SSL_CTX_need_tmp_RSA(ctx->ssl_ctx)) + SSL_CTX_set_tmp_rsa_callback(ctx->ssl_ctx, ssl_gen_rsa_key); + SSL_CTX_set_tmp_dh_callback(ctx->ssl_ctx, ssl_tmp_dh_callback); + + return ssl_iostream_context_set(ctx, set); +} + +int ssl_iostream_context_init_client(const char *source, + const struct ssl_iostream_settings *set, + struct ssl_iostream_context **ctx_r) +{ + struct ssl_iostream_context *ctx; + SSL_CTX *ssl_ctx; + + ssl_iostream_init_global(); + if ((ssl_ctx = SSL_CTX_new(SSLv23_client_method())) == NULL) { + i_error("SSL_CTX_new() failed: %s", ssl_iostream_error()); + return -1; + } + + ctx = i_new(struct ssl_iostream_context, 1); + ctx->ssl_ctx = ssl_ctx; + ctx->client_ctx = TRUE; + if (ssl_iostream_context_init_common(ctx, source, set) < 0) { + ssl_iostream_context_deinit(&ctx); + return -1; + } + *ctx_r = ctx; + return 0; +} + +int ssl_iostream_context_init_server(const char *source, + const struct ssl_iostream_settings *set, + struct ssl_iostream_context **ctx_r) +{ + struct ssl_iostream_context *ctx; + SSL_CTX *ssl_ctx; + + ssl_iostream_init_global(); + if ((ssl_ctx = SSL_CTX_new(SSLv23_server_method())) == NULL) { + i_error("SSL_CTX_new() failed: %s", ssl_iostream_error()); + return -1; + } + + ctx = i_new(struct ssl_iostream_context, 1); + ctx->ssl_ctx = ssl_ctx; + if (ssl_iostream_context_init_common(ctx, source, set) < 0) { + ssl_iostream_context_deinit(&ctx); + return -1; + } + *ctx_r = ctx; + return 0; +} + +void ssl_iostream_context_deinit(struct ssl_iostream_context **_ctx) +{ + struct ssl_iostream_context *ctx = *_ctx; + + *_ctx = NULL; + SSL_CTX_free(ctx->ssl_ctx); + ssl_iostream_context_free_params(ctx); + pool_unref(&ctx->pool); + i_free(ctx); +} + +static void ssl_iostream_deinit_global(void) +{ + EVP_cleanup(); + ERR_free_strings(); +} + +static void ssl_iostream_init_global(void) +{ + static char dovecot[] = "dovecot"; + unsigned char buf; + + if (ssl_global_initialized) + return; + + atexit(ssl_iostream_deinit_global); + ssl_global_initialized = TRUE; + SSL_library_init(); + SSL_load_error_strings(); + + dovecot_ssl_extdata_index = + SSL_get_ex_new_index(0, dovecot, NULL, NULL, NULL); + + /* PRNG initialization might want to use /dev/urandom, make sure it + 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. */ + (void)RAND_bytes(&buf, 1); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-ssl-iostream/iostream-openssl-params.c Mon Jan 31 18:40:27 2011 +0200 @@ -0,0 +1,129 @@ +/* Copyright (c) 2009 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "buffer.h" +#include "iostream-openssl.h" + +/* 2 or 5. Haven't seen their difference explained anywhere, but 2 is the + default.. */ +#define DH_GENERATOR 2 + +static int dh_param_bitsizes[] = { 512, 1024 }; + +static int generate_dh_parameters(int bitsize, buffer_t *output) +{ + DH *dh; + unsigned char *p; + int len, len2; + + dh = DH_generate_parameters(bitsize, DH_GENERATOR, NULL, NULL); + if (dh == NULL) { + i_error("DH_generate_parameters(bits=%d, gen=%d) failed: %s", + bitsize, DH_GENERATOR, ssl_iostream_error()); + return -1; + } + + len = i2d_DHparams(dh, NULL); + if (len < 0) { + i_error("i2d_DHparams() failed: %s", ssl_iostream_error()); + DH_free(dh); + return -1; + } + + buffer_append(output, &bitsize, sizeof(bitsize)); + buffer_append(output, &len, sizeof(len)); + + p = buffer_append_space_unsafe(output, len); + len2 = i2d_DHparams(dh, &p); + i_assert(len == len2); + DH_free(dh); + return 0; +} + +int ssl_iostream_generate_params(buffer_t *output) +{ + unsigned int i; + + for (i = 0; i < N_ELEMENTS(dh_param_bitsizes); i++) { + if (generate_dh_parameters(dh_param_bitsizes[i], output) < 0) + return -1; + } + buffer_append_zero(output, sizeof(int)); + return 0; +} + +static int read_int(const unsigned char **data, const unsigned char *end) +{ + unsigned int len = end - *data; + int ret; + + if (len < sizeof(ret)) + return -1; + memcpy(&ret, *data, sizeof(ret)); + *data += sizeof(ret); + return ret; +} + +static int +read_dh_parameters_next(struct ssl_iostream_context *ctx, + const unsigned char **data, const unsigned char *end) +{ + const unsigned char *dbuf; + DH *dh; + int bits, len, ret = 1; + + /* get bit size. 0 ends the DH parameters list. */ + if ((bits = read_int(data, end)) <= 0) + return bits; + + /* get data size */ + if ((len = read_int(data, end)) <= 0 || end - *data < len) + return -1; + + dbuf = *data; + dh = d2i_DHparams(NULL, &dbuf, len); + *data += len; + + if (dh == NULL) + return -1; + + switch (bits) { + case 512: + ctx->dh_512 = dh; + break; + case 1024: + ctx->dh_1024 = dh; + break; + default: + ret = -1; + break; + } + return ret; +} + +int ssl_iostream_context_import_params(struct ssl_iostream_context *ctx, + const buffer_t *input) +{ + const unsigned char *data, *end; + int ret; + + ssl_iostream_context_free_params(ctx); + + data = input->data; + end = data + input->used; + while ((ret = read_dh_parameters_next(ctx, &data, end)) > 0) ; + + return ret < 0 || data != end ? -1 : 0; +} + +void ssl_iostream_context_free_params(struct ssl_iostream_context *ctx) +{ + if (ctx->dh_512 != NULL) { + DH_free(ctx->dh_512); + ctx->dh_512 = NULL; + } + if (ctx->dh_1024 != NULL) { + DH_free(ctx->dh_1024); + ctx->dh_1024 = NULL; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-ssl-iostream/iostream-openssl.c Mon Jan 31 18:40:27 2011 +0200 @@ -0,0 +1,482 @@ +/* Copyright (c) 2009 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "istream.h" +#include "ostream.h" +#include "iostream-openssl.h" + +#include <openssl/err.h> + +static void ssl_iostream_free(struct ssl_iostream *ssl_io); + +static void ssl_info_callback(const SSL *ssl, int where, int ret) +{ + struct ssl_iostream *ssl_io; + + ssl_io = SSL_get_ex_data(ssl, dovecot_ssl_extdata_index); + if ((where & SSL_CB_ALERT) != 0) { + i_warning("%s: SSL alert: where=0x%x, ret=%d: %s %s", + ssl_io->source, where, ret, + SSL_alert_type_string_long(ret), + SSL_alert_desc_string_long(ret)); + } else if (ret == 0) { + i_warning("%s: SSL failed: where=0x%x: %s", + ssl_io->source, where, SSL_state_string_long(ssl)); + } else { + i_warning("%s: SSL: where=0x%x, ret=%d: %s", + ssl_io->source, where, ret, + SSL_state_string_long(ssl)); + } +} + +static int +ssl_iostream_use_certificate(struct ssl_iostream *ssl_io, 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_error("BIO_new_mem_buf() failed: %s", ssl_iostream_error()); + return -1; + } + + x = PEM_read_bio_X509(in, NULL, NULL, NULL); + if (x != NULL) { + ret = SSL_use_certificate(ssl_io->ssl, x); + if (ERR_peek_error() != 0) + ret = 0; + X509_free(x); + } + BIO_free(in); + + if (ret == 0) { + i_error("%s: Can't load ssl_cert: %s", ssl_io->source, + ssl_iostream_get_use_certificate_error(cert)); + return -1; + } + return 0; +} + +static int +ssl_iostream_use_key(struct ssl_iostream *ssl_io, + const struct ssl_iostream_settings *set) +{ + EVP_PKEY *pkey; + int ret = 0; + + if (ssl_iostream_load_key(set, ssl_io->source, &pkey) < 0) + return -1; + if (SSL_use_PrivateKey(ssl_io->ssl, pkey) != 1) { + i_error("%s: Can't load SSL private key: %s", + ssl_io->source, ssl_iostream_key_load_error()); + ret = -1; + } + EVP_PKEY_free(pkey); + return ret; +} + +static int +ssl_iostream_verify_client_cert(int preverify_ok, X509_STORE_CTX *ctx) +{ + int ssl_extidx = SSL_get_ex_data_X509_STORE_CTX_idx(); + SSL *ssl; + struct ssl_iostream *ssl_io; + + ssl = X509_STORE_CTX_get_ex_data(ctx, ssl_extidx); + ssl_io = SSL_get_ex_data(ssl, dovecot_ssl_extdata_index); + ssl_io->cert_received = TRUE; + + if (ssl_io->verbose || + (ssl_io->verbose_invalid_cert && !preverify_ok)) { + char buf[1024]; + X509_NAME *subject; + + subject = X509_get_subject_name(ctx->current_cert); + if (X509_NAME_oneline(subject, buf, sizeof(buf)) == NULL) + buf[0] = '\0'; + else + buf[sizeof(buf)-1] = '\0'; /* just in case.. */ + 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 (!preverify_ok) { + ssl_io->cert_broken = TRUE; + if (ssl_io->require_valid_cert) + return 0; + } + return 1; +} + +static int +ssl_iostream_set(struct ssl_iostream *ssl_io, + const struct ssl_iostream_settings *set) +{ + const struct ssl_iostream_settings *ctx_set = ssl_io->ctx->set; + + if (set->verbose) + SSL_set_info_callback(ssl_io->ssl, ssl_info_callback); + + if (set->cipher_list != NULL && + strcmp(ctx_set->cipher_list, set->cipher_list) != 0) { + if (!SSL_set_cipher_list(ssl_io->ssl, set->cipher_list)) { + i_error("%s: Can't set cipher list to '%s': %s", + ssl_io->source, set->cipher_list, + ssl_iostream_error()); + } + return -1; + + } + if (set->cert != NULL && strcmp(ctx_set->cert, set->cert) != 0) { + if (ssl_iostream_use_certificate(ssl_io, set->cert) < 0) + return -1; + } + if (set->key != NULL && strcmp(ctx_set->key, set->key) != 0) { + if (ssl_iostream_use_key(ssl_io, set) < 0) + return -1; + } + if (set->verify_remote_cert) { + SSL_set_verify(ssl_io->ssl, + SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, + ssl_iostream_verify_client_cert); + } + + if (set->cert_username_field != NULL) { + ssl_io->username_nid = OBJ_txt2nid(set->cert_username_field); + if (ssl_io->username_nid == NID_undef) { + i_error("%s: Invalid cert_username_field: %s", + ssl_io->source, set->cert_username_field); + } + } else { + ssl_io->username_nid = ssl_io->ctx->username_nid; + } + + ssl_io->verbose = set->verbose; + ssl_io->verbose_invalid_cert = set->verbose_invalid_cert; + ssl_io->require_valid_cert = set->require_valid_cert; + return 0; +} + +int io_stream_create_ssl(struct ssl_iostream_context *ctx, const char *source, + const struct ssl_iostream_settings *set, + struct istream **input, struct ostream **output, + struct ssl_iostream **iostream_r) +{ + struct ssl_iostream *ssl_io; + SSL *ssl; + BIO *bio_int, *bio_ext; + int ret; + + ssl = SSL_new(ctx->ssl_ctx); + if (ssl == NULL) { + i_error("SSL_new() failed: %s", ssl_iostream_error()); + return -1; + } + + if (BIO_new_bio_pair(&bio_int, 0, &bio_ext, 0) != 1) { + i_error("BIO_new_bio_pair() failed: %s", ssl_iostream_error()); + SSL_free(ssl); + return -1; + } + + ssl_io = i_new(struct ssl_iostream, 1); + ssl_io->refcount = 1; + ssl_io->ctx = ctx; + ssl_io->ssl = ssl; + ssl_io->bio_ext = bio_ext; + ssl_io->plain_input = *input; + ssl_io->plain_output = *output; + ssl_io->source = i_strdup(source); + SSL_set_bio(ssl_io->ssl, bio_int, bio_int); + SSL_set_ex_data(ssl_io->ssl, dovecot_ssl_extdata_index, ssl_io); + + i_stream_ref(ssl_io->plain_input); + o_stream_ref(ssl_io->plain_output); + + T_BEGIN { + ret = ssl_iostream_set(ssl_io, set); + } T_END; + if (ret < 0) { + ssl_iostream_free(ssl_io); + return -1; + } + + *input = i_stream_create_ssl(ssl_io); + *output = o_stream_create_ssl(ssl_io); + + ssl_io->ssl_output = *output; + *iostream_r = ssl_io; + return 0; +} + +static void ssl_iostream_free(struct ssl_iostream *ssl_io) +{ + i_stream_unref(&ssl_io->plain_input); + o_stream_unref(&ssl_io->plain_output); + BIO_free(ssl_io->bio_ext); + SSL_free(ssl_io->ssl); + i_free(ssl_io->last_error); + i_free(ssl_io->source); + i_free(ssl_io); +} + +void ssl_iostream_unref(struct ssl_iostream **_ssl_io) +{ + struct ssl_iostream *ssl_io = *_ssl_io; + + *_ssl_io = NULL; + + i_assert(ssl_io->refcount > 0); + if (--ssl_io->refcount >= 0) + return; + + ssl_iostream_free(ssl_io); +} + +static bool ssl_iostream_bio_output(struct ssl_iostream *ssl_io) +{ + size_t bytes, max_bytes; + ssize_t sent; + unsigned char buffer[1024]; + bool bytes_sent = FALSE; + int ret; + + while ((bytes = BIO_ctrl_pending(ssl_io->bio_ext)) > 0) { + max_bytes = o_stream_get_buffer_avail_size(ssl_io->plain_output); + if (bytes > max_bytes) { + if (max_bytes == 0) { + /* wait until output buffer clears */ + break; + } + bytes = max_bytes; + } + if (bytes > sizeof(buffer)) + bytes = sizeof(buffer); + + ret = BIO_read(ssl_io->bio_ext, buffer, bytes); + i_assert(ret == (int)bytes); + + sent = o_stream_send(ssl_io->plain_output, buffer, bytes); + i_assert(sent == (ssize_t)bytes); + bytes_sent = TRUE; + } + return bytes_sent; +} + +static bool ssl_iostream_bio_input(struct ssl_iostream *ssl_io) +{ + const unsigned char *data; + size_t size; + bool bytes_read = FALSE; + int ret; + + while (BIO_ctrl_get_read_request(ssl_io->bio_ext) > 0) { + (void)i_stream_read_data(ssl_io->plain_input, &data, &size, 0); + if (size == 0) { + /* wait for more input */ + break; + } + ret = BIO_write(ssl_io->bio_ext, data, size); + i_assert(ret == (ssize_t)size); + + i_stream_skip(ssl_io->plain_input, size); + bytes_read = TRUE; + } + return bytes_read; +} + +bool ssl_iostream_bio_sync(struct ssl_iostream *ssl_io) +{ + bool ret; + + ret = ssl_iostream_bio_output(ssl_io); + if (ssl_iostream_bio_input(ssl_io)) + ret = TRUE; + return ret; +} + +int ssl_iostream_handle_error(struct ssl_iostream *ssl_io, int ret, + const char *func_name) +{ + const char *errstr = NULL; + int err; + + err = SSL_get_error(ssl_io->ssl, ret); + switch (err) { + case SSL_ERROR_WANT_WRITE: + if (!ssl_iostream_bio_sync(ssl_io)) + return 0; + return 1; + case SSL_ERROR_WANT_READ: + if (!ssl_iostream_bio_sync(ssl_io)) + return 0; + return 1; + case SSL_ERROR_SYSCALL: + /* eat up the error queue */ + if (ERR_peek_error() != 0) { + errstr = ssl_iostream_error(); + errno = EINVAL; + } else if (ret != 0) { + errstr = strerror(errno); + } else { + /* EOF. */ + errno = ECONNRESET; + errstr = "Disconnected"; + break; + } + errstr = t_strdup_printf("%s syscall failed: %s", + func_name, errstr); + break; + case SSL_ERROR_ZERO_RETURN: + /* clean connection closing */ + errno = ECONNRESET; + break; + case SSL_ERROR_SSL: + errstr = t_strdup_printf("%s failed: %s", + func_name, ssl_iostream_error()); + errno = EINVAL; + break; + default: + errstr = t_strdup_printf("%s failed: unknown failure %d (%s)", + func_name, err, ssl_iostream_error()); + errno = EINVAL; + break; + } + + if (errstr != NULL) { + i_free(ssl_io->last_error); + ssl_io->last_error = i_strdup(errstr); + } + return -1; +} + +int ssl_iostream_handshake(struct ssl_iostream *ssl_io) +{ + int ret; + + i_assert(!ssl_io->handshaked); + + if (ssl_io->ctx->client_ctx) { + while ((ret = SSL_connect(ssl_io->ssl)) <= 0) { + ret = ssl_iostream_handle_error(ssl_io, ret, + "SSL_connect()"); + if (ret <= 0) + return ret; + } + } else { + while ((ret = SSL_accept(ssl_io->ssl)) <= 0) { + ret = ssl_iostream_handle_error(ssl_io, ret, + "SSL_accept()"); + if (ret <= 0) + return ret; + } + } + (void)ssl_iostream_bio_sync(ssl_io); + + i_free_and_null(ssl_io->last_error); + ssl_io->handshaked = TRUE; + + if (ssl_io->handshake_callback != NULL) { + if (ssl_io->handshake_callback(ssl_io->handshake_context) < 0) { + errno = EINVAL; + return -1; + } + } + if (ssl_io->ssl_output != NULL) + (void)o_stream_flush(ssl_io->ssl_output); + return 1; +} + +void ssl_iostream_set_handshake_callback(struct ssl_iostream *ssl_io, + int (*callback)(void *context), + void *context) +{ + ssl_io->handshake_callback = callback; + ssl_io->handshake_context = context; +} + +bool ssl_iostream_is_handshaked(const struct ssl_iostream *ssl_io) +{ + return ssl_io->handshaked; +} + +bool ssl_iostream_has_valid_client_cert(const struct ssl_iostream *ssl_io) +{ + return ssl_io->cert_received && !ssl_io->cert_broken; +} + +bool ssl_iostream_has_broken_client_cert(struct ssl_iostream *ssl_io) +{ + return ssl_io->cert_received && ssl_io->cert_broken; +} + +const char *ssl_iostream_get_peer_name(struct ssl_iostream *ssl_io) +{ + X509 *x509; + char *name; + int len; + + if (!ssl_iostream_has_valid_client_cert(ssl_io)) + return NULL; + + x509 = SSL_get_peer_certificate(ssl_io->ssl); + if (x509 == NULL) + return NULL; /* we should have had it.. */ + + len = X509_NAME_get_text_by_NID(X509_get_subject_name(x509), + ssl_io->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_io->username_nid, + name, len + 1) < 0) + name = ""; + else if (strlen(name) != (size_t)len) { + /* NUL characters in name. Someone's trying to fake + being another user? Don't allow it. */ + name = ""; + } + } + X509_free(x509); + + return *name == '\0' ? NULL : name; +} + +const char *ssl_iostream_get_security_string(struct ssl_iostream *ssl_io) +{ + SSL_CIPHER *cipher; +#ifdef HAVE_SSL_COMPRESSION + const COMP_METHOD *comp; +#endif + const char *comp_str; + int bits, alg_bits; + + if (!ssl_io->handshaked) + return ""; + + cipher = SSL_get_current_cipher(ssl_io->ssl); + bits = SSL_CIPHER_get_bits(cipher, &alg_bits); +#ifdef HAVE_SSL_COMPRESSION + comp = SSL_get_current_compression(ssl_io->ssl); + comp_str = comp == NULL ? "" : + t_strconcat(" ", SSL_COMP_get_name(comp), NULL); +#else + comp_str = ""; +#endif + return t_strdup_printf("%s with cipher %s (%d/%d bits)", + SSL_get_version(ssl_io->ssl), + SSL_CIPHER_get_name(cipher), + bits, alg_bits); +} + +const char *ssl_iostream_get_last_error(struct ssl_iostream *ssl_io) +{ + return ssl_io->last_error; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-ssl-iostream/iostream-openssl.h Mon Jan 31 18:40:27 2011 +0200 @@ -0,0 +1,69 @@ +#ifndef IOSTREAM_OPENSSL_H +#define IOSTREAM_OPENSSL_H + +#include "iostream-ssl.h" + +#include <openssl/ssl.h> + +struct ssl_iostream_context { + SSL_CTX *ssl_ctx; + + pool_t pool; + const struct ssl_iostream_settings *set; + /* Used as logging prefix, e.g. "client" or "server" */ + const char *source; + + DH *dh_512, *dh_1024; + int username_nid; + + unsigned int client_ctx:1; +}; + +struct ssl_iostream { + int refcount; + struct ssl_iostream_context *ctx; + + const struct ssl_iostream_settings *set; + + SSL *ssl; + BIO *bio_ext; + + struct istream *plain_input; + struct ostream *plain_output; + struct ostream *ssl_output; + + char *source; + char *last_error; + + /* copied settings */ + bool verbose, verbose_invalid_cert, require_valid_cert; + int username_nid; + + int (*handshake_callback)(void *context); + void *handshake_context; + + unsigned int handshaked:1; + unsigned int cert_received:1; + unsigned int cert_broken:1; +}; + +extern int dovecot_ssl_extdata_index; + +struct istream *i_stream_create_ssl(struct ssl_iostream *ssl_io); +struct ostream *o_stream_create_ssl(struct ssl_iostream *ssl_io); +void ssl_iostream_unref(struct ssl_iostream **ssl_io); + +int ssl_iostream_load_key(const struct ssl_iostream_settings *set, + const char *key_source, EVP_PKEY **pkey_r); +const char *ssl_iostream_get_use_certificate_error(const char *cert); + +bool ssl_iostream_bio_sync(struct ssl_iostream *ssl_io); +int ssl_iostream_handle_error(struct ssl_iostream *ssl_io, int ret, + const char *func_name); + +const char *ssl_iostream_error(void); +const char *ssl_iostream_key_load_error(void); + +void ssl_iostream_context_free_params(struct ssl_iostream_context *ctx); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-ssl-iostream/iostream-ssl.h Mon Jan 31 18:40:27 2011 +0200 @@ -0,0 +1,51 @@ +#ifndef IOSTREAM_SSL_H +#define IOSTREAM_SSL_H + +struct ssl_iostream; +struct ssl_iostream_context; + +struct ssl_iostream_settings { + const char *cipher_list; + const char *ca, *ca_dir; + const char *cert; + const char *key; + const char *key_password; + const char *cert_username_field; + + bool verbose, verbose_invalid_cert; + bool verify_remote_cert; + bool require_valid_cert; +}; + +int io_stream_create_ssl(struct ssl_iostream_context *ctx, const char *source, + const struct ssl_iostream_settings *set, + struct istream **input, struct ostream **output, + struct ssl_iostream **iostream_r); +/* returned input and output streams must also be unreferenced */ +void ssl_iostream_unref(struct ssl_iostream **ssl_io); + +int ssl_iostream_handshake(struct ssl_iostream *ssl_io); +void ssl_iostream_set_handshake_callback(struct ssl_iostream *ssl_io, + int (*callback)(void *context), + void *context); + +bool ssl_iostream_is_handshaked(const struct ssl_iostream *ssl_io); +bool ssl_iostream_has_valid_client_cert(const struct ssl_iostream *ssl_io); +bool ssl_iostream_has_broken_client_cert(struct ssl_iostream *ssl_io); +const char *ssl_iostream_get_peer_name(struct ssl_iostream *ssl_io); +const char *ssl_iostream_get_security_string(struct ssl_iostream *ssl_io); +const char *ssl_iostream_get_last_error(struct ssl_iostream *ssl_io); + +int ssl_iostream_generate_params(buffer_t *output); +int ssl_iostream_context_import_params(struct ssl_iostream_context *ctx, + const buffer_t *input); + +int ssl_iostream_context_init_client(const char *source, + const struct ssl_iostream_settings *set, + struct ssl_iostream_context **ctx_r); +int ssl_iostream_context_init_server(const char *source, + const struct ssl_iostream_settings *set, + struct ssl_iostream_context **ctx_r); +void ssl_iostream_context_deinit(struct ssl_iostream_context **ctx); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-ssl-iostream/istream-openssl.c Mon Jan 31 18:40:27 2011 +0200 @@ -0,0 +1,74 @@ +/* Copyright (c) 2009 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "istream-internal.h" +#include "iostream-openssl.h" + +struct ssl_istream { + struct istream_private istream; + struct ssl_iostream *ssl_io; + bool seen_eof; +}; + +static void i_stream_ssl_destroy(struct iostream_private *stream) +{ + struct ssl_istream *sstream = (struct ssl_istream *)stream; + + ssl_iostream_unref(&sstream->ssl_io); +} + +static ssize_t i_stream_ssl_read(struct istream_private *stream) +{ + struct ssl_istream *sstream = (struct ssl_istream *)stream; + size_t size; + ssize_t ret; + + if (sstream->seen_eof) { + stream->istream.eof = TRUE; + return -1; + } + if (!sstream->ssl_io->handshaked) { + if ((ret = ssl_iostream_handshake(sstream->ssl_io)) <= 0) { + if (ret < 0) + stream->istream.stream_errno = errno; + return ret; + } + } + + if (!i_stream_get_buffer_space(stream, 1, &size)) + return -2; + + while ((ret = SSL_read(sstream->ssl_io->ssl, + stream->w_buffer + stream->pos, size)) <= 0) { + ret = ssl_iostream_handle_error(sstream->ssl_io, ret, + "SSL_read"); + if (ret <= 0) { + if (ret < 0) { + stream->istream.stream_errno = errno; + stream->istream.eof = TRUE; + sstream->seen_eof = TRUE; + } + return ret; + } + (void)ssl_iostream_bio_sync(sstream->ssl_io); + } + stream->pos += ret; + return ret; +} + +struct istream *i_stream_create_ssl(struct ssl_iostream *ssl_io) +{ + struct ssl_istream *sstream; + + ssl_io->refcount++; + + sstream = i_new(struct ssl_istream, 1); + sstream->ssl_io = ssl_io; + sstream->istream.iostream.destroy = i_stream_ssl_destroy; + sstream->istream.max_buffer_size = + ssl_io->plain_input->real_stream->max_buffer_size; + sstream->istream.read = i_stream_ssl_read; + + sstream->istream.istream.readable_fd = FALSE; + return i_stream_create(&sstream->istream, NULL, -1); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-ssl-iostream/ostream-openssl.c Mon Jan 31 18:40:27 2011 +0200 @@ -0,0 +1,185 @@ +/* Copyright (c) 2009 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "buffer.h" +#include "ostream-internal.h" +#include "iostream-openssl.h" + +struct ssl_ostream { + struct ostream_private ostream; + struct ssl_iostream *ssl_io; + buffer_t *buffer; +}; + +static void i_stream_ssl_destroy(struct iostream_private *stream) +{ + struct ssl_ostream *sstream = (struct ssl_ostream *)stream; + + sstream->ssl_io->ssl_output = NULL; + ssl_iostream_unref(&sstream->ssl_io); + i_free(sstream->buffer); +} + +static size_t +o_stream_ssl_buffer(struct ssl_ostream *sstream, const struct const_iovec *iov, + unsigned int iov_count, size_t bytes_sent) +{ + size_t avail, skip_left, size; + unsigned int i; + + if (sstream->buffer == NULL) + sstream->buffer = buffer_create_dynamic(default_pool, 4096); + + skip_left = bytes_sent; + for (i = 0; i < iov_count; i++) { + if (skip_left < iov[i].iov_len) + break; + skip_left -= iov[i].iov_len; + } + + avail = sstream->ostream.max_buffer_size > sstream->buffer->used ? + sstream->ostream.max_buffer_size - sstream->buffer->used : 0; + if (i < iov_count && skip_left > 0) { + size = I_MIN(iov[i].iov_len - skip_left, avail); + buffer_append(sstream->buffer, + CONST_PTR_OFFSET(iov[i].iov_base, skip_left), + size); + bytes_sent += size; + avail -= size; + if (size != iov[i].iov_len) + i = iov_count; + } + for (; i < iov_count; i++) { + size = I_MIN(iov[i].iov_len, avail); + buffer_append(sstream->buffer, iov[i].iov_base, size); + bytes_sent += size; + avail -= size; + + if (size != iov[i].iov_len) + break; + } + + sstream->ostream.ostream.offset += bytes_sent; + return bytes_sent; +} + +static int o_stream_ssl_flush_buffer(struct ssl_ostream *sstream) +{ + size_t pos = 0; + int ret; + + while (pos < sstream->buffer->used) { + ret = SSL_write(sstream->ssl_io->ssl, + CONST_PTR_OFFSET(sstream->buffer->data, pos), + sstream->buffer->used - pos); + if (ret <= 0) { + ret = ssl_iostream_handle_error(sstream->ssl_io, ret, + "SSL_write"); + if (ret <= 0) { + if (ret < 0) { + sstream->ostream.ostream.stream_errno = + errno; + } + buffer_delete(sstream->buffer, 0, pos); + return ret; + } + } else { + pos += ret; + (void)ssl_iostream_bio_sync(sstream->ssl_io); + } + } + buffer_delete(sstream->buffer, 0, pos); + return 1; +} + +static int o_stream_ssl_flush(struct ostream_private *stream) +{ + struct ssl_ostream *sstream = (struct ssl_ostream *)stream; + int ret; + + if (!sstream->ssl_io->handshaked) { + if ((ret = ssl_iostream_handshake(sstream->ssl_io)) <= 0) { + if (ret < 0) + stream->ostream.stream_errno = errno; + return ret; + } + } + + if (sstream->buffer != NULL && sstream->buffer->used > 0) { + if ((ret = o_stream_ssl_flush_buffer(sstream)) <= 0) + return ret; + } + return 1; +} + +static ssize_t +o_stream_ssl_sendv(struct ostream_private *stream, + const struct const_iovec *iov, unsigned int iov_count) +{ + struct ssl_ostream *sstream = (struct ssl_ostream *)stream; + unsigned int i; + size_t bytes_sent = 0; + size_t pos; + int ret = 0; + + if (o_stream_flush(&stream->ostream) <= 0) + return o_stream_ssl_buffer(sstream, iov, iov_count, 0); + + for (i = 0, pos = 0; i < iov_count; ) { + ret = SSL_write(sstream->ssl_io->ssl, + CONST_PTR_OFFSET(iov[i].iov_base, pos), + iov[i].iov_len - pos); + if (ret <= 0) { + ret = ssl_iostream_handle_error(sstream->ssl_io, ret, + "SSL_write"); + if (ret <= 0) { + if (ret < 0) + stream->ostream.stream_errno = errno; + break; + } + } else { + bytes_sent += ret; + if ((size_t)ret < iov[i].iov_len) + pos += ret; + else { + i++; + pos = 0; + } + (void)ssl_iostream_bio_sync(sstream->ssl_io); + } + } + bytes_sent = o_stream_ssl_buffer(sstream, iov, iov_count, bytes_sent); + return bytes_sent != 0 ? (ssize_t)bytes_sent : ret; +} + +static int plain_flush_callback(struct ssl_ostream *sstream) +{ + int ret; + + if ((ret = o_stream_flush(sstream->ssl_io->plain_output)) < 0) + return 1; + + if (ret > 0) + return o_stream_flush(&sstream->ostream.ostream); + return 1; +} + +struct ostream *o_stream_create_ssl(struct ssl_iostream *ssl_io) +{ + struct ssl_ostream *sstream; + + ssl_io->refcount++; + + sstream = i_new(struct ssl_ostream, 1); + sstream->ssl_io = ssl_io; + sstream->ostream.max_buffer_size = + ssl_io->plain_output->real_stream->max_buffer_size; + sstream->ostream.iostream.destroy = i_stream_ssl_destroy; + sstream->ostream.sendv = o_stream_ssl_sendv; + sstream->ostream.flush = o_stream_ssl_flush; + + o_stream_set_flush_callback(ssl_io->plain_output, + plain_flush_callback, sstream); + + return o_stream_create(&sstream->ostream); +}