Mercurial > dovecot > core-2.2
view src/lib-ssl-iostream/iostream-openssl-context.c @ 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 | |
children | 733ac4aba089 |
line wrap: on
line source
/* 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); }