X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/ba0846407cf2bcd79bb969145dd839ba90616731..exim-4_80_RC6:/src/src/tls-openssl.c diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index dc341d326..43b79634e 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -1,10 +1,8 @@ -/* $Cambridge: exim/src/src/tls-openssl.c,v 1.24 2010/06/05 09:32:31 pdp Exp $ */ - /************************************************* * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2009 */ +/* Copyright (c) University of Cambridge 1995 - 2012 */ /* See the file NOTICE for conditions of use and distribution. */ /* This module provides the TLS (aka SSL) support for Exim using the OpenSSL @@ -22,6 +20,18 @@ functions from the OpenSSL library. */ #include #include #include +#ifdef EXPERIMENTAL_OCSP +#include +#endif + +#ifdef EXPERIMENTAL_OCSP +#define EXIM_OCSP_SKEW_SECONDS (300L) +#define EXIM_OCSP_MAX_AGE (-1L) +#endif + +#if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT) +#define EXIM_HAVE_OPENSSL_TLSEXT +#endif /* Structure for collecting random data for seeding. */ @@ -36,6 +46,9 @@ static BOOL verify_callback_called = FALSE; static const uschar *sid_ctx = US"exim"; static SSL_CTX *ctx = NULL; +#ifdef EXIM_HAVE_OPENSSL_TLSEXT +static SSL_CTX *ctx_sni = NULL; +#endif static SSL *ssl = NULL; static char ssl_errstring[256]; @@ -43,8 +56,39 @@ static char ssl_errstring[256]; static int ssl_session_timeout = 200; static BOOL verify_optional = FALSE; +static BOOL reexpand_tls_files_for_sni = FALSE; + + +typedef struct tls_ext_ctx_cb { + uschar *certificate; + uschar *privatekey; +#ifdef EXPERIMENTAL_OCSP + uschar *ocsp_file; + uschar *ocsp_file_expanded; + OCSP_RESPONSE *ocsp_response; +#endif + uschar *dhparam; + /* these are cached from first expand */ + uschar *server_cipher_list; + /* only passed down to tls_error: */ + host_item *host; +} tls_ext_ctx_cb; + +/* should figure out a cleanup of API to handle state preserved per +implementation, for various reasons, which can be void * in the APIs. +For now, we hack around it. */ +tls_ext_ctx_cb *static_cbinfo = NULL; +static int +setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional); +/* Callbacks */ +#ifdef EXIM_HAVE_OPENSSL_TLSEXT +static int tls_servername_cb(SSL *s, int *ad ARG_UNUSED, void *arg); +#endif +#ifdef EXPERIMENTAL_OCSP +static int tls_stapling_cb(SSL *s, void *arg); +#endif /************************************************* @@ -203,8 +247,8 @@ return 1; /* accept */ *************************************************/ /* The SSL library functions call this from time to time to indicate what they -are doing. We copy the string to the debugging output when the level is high -enough. +are doing. We copy the string to the debugging output when TLS debugging has +been requested. Arguments: s the SSL connection @@ -231,53 +275,424 @@ DEBUG(D_tls) debug_printf("SSL info: %s\n", SSL_state_string_long(s)); /* If dhparam is set, expand it, and load up the parameters for DH encryption. Arguments: - dhparam DH parameter file + dhparam DH parameter file or fixed parameter identity string host connected host, if client; NULL if server Returns: TRUE if OK (nothing to set up, or setup worked) */ static BOOL -init_dh(uschar *dhparam, host_item *host) +init_dh(SSL_CTX *sctx, uschar *dhparam, host_item *host) { -BOOL yield = TRUE; BIO *bio; DH *dh; uschar *dhexpanded; +const char *pem; if (!expand_check(dhparam, US"tls_dhparam", &dhexpanded)) return FALSE; -if (dhexpanded == NULL) return TRUE; - -if ((bio = BIO_new_file(CS dhexpanded, "r")) == NULL) +if (dhexpanded == NULL || *dhexpanded == '\0') { - tls_error(string_sprintf("could not read dhparams file %s", dhexpanded), - host, (uschar *)strerror(errno)); - yield = FALSE; + bio = BIO_new_mem_buf(CS std_dh_prime_default(), -1); } -else +else if (dhexpanded[0] == '/') { - if ((dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL)) == NULL) + bio = BIO_new_file(CS dhexpanded, "r"); + if (bio == NULL) { tls_error(string_sprintf("could not read dhparams file %s", dhexpanded), - host, NULL); - yield = FALSE; + host, US strerror(errno)); + return FALSE; } - else + } +else + { + if (Ustrcmp(dhexpanded, "none") == 0) { - SSL_CTX_set_tmp_dh(ctx, dh); - DEBUG(D_tls) - debug_printf("Diffie-Hellman initialized from %s with %d-bit key\n", - dhexpanded, 8*DH_size(dh)); - DH_free(dh); + DEBUG(D_tls) debug_printf("Requested no DH parameters.\n"); + return TRUE; + } + + pem = std_dh_prime_named(dhexpanded); + if (!pem) + { + tls_error(string_sprintf("Unknown standard DH prime \"%s\"", dhexpanded), + host, US strerror(errno)); + return FALSE; } + bio = BIO_new_mem_buf(CS pem, -1); + } + +dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL); +if (dh == NULL) + { BIO_free(bio); + tls_error(string_sprintf("Could not read tls_dhparams \"%s\"", dhexpanded), + host, NULL); + return FALSE; + } + +/* Even if it is larger, we silently return success rather than cause things + * to fail out, so that a too-large DH will not knock out all TLS; it's a + * debatable choice. */ +if ((8*DH_size(dh)) > tls_dh_max_bits) + { + DEBUG(D_tls) + debug_printf("dhparams file %d bits, is > tls_dh_max_bits limit of %d", + 8*DH_size(dh), tls_dh_max_bits); + } +else + { + SSL_CTX_set_tmp_dh(sctx, dh); + DEBUG(D_tls) + debug_printf("Diffie-Hellman initialized from %s with %d-bit prime\n", + dhexpanded ? dhexpanded : US"default", 8*DH_size(dh)); + } + +DH_free(dh); +BIO_free(bio); + +return TRUE; +} + + + + +#ifdef EXPERIMENTAL_OCSP +/************************************************* +* Load OCSP information into state * +*************************************************/ + +/* Called to load the OCSP response from the given file into memory, once +caller has determined this is needed. Checks validity. Debugs a message +if invalid. + +ASSUMES: single response, for single cert. + +Arguments: + sctx the SSL_CTX* to update + cbinfo various parts of session state + expanded the filename putatively holding an OCSP response + +*/ + +static void +ocsp_load_response(SSL_CTX *sctx, + tls_ext_ctx_cb *cbinfo, + const uschar *expanded) +{ +BIO *bio; +OCSP_RESPONSE *resp; +OCSP_BASICRESP *basic_response; +OCSP_SINGLERESP *single_response; +ASN1_GENERALIZEDTIME *rev, *thisupd, *nextupd; +X509_STORE *store; +unsigned long verify_flags; +int status, reason, i; + +cbinfo->ocsp_file_expanded = string_copy(expanded); +if (cbinfo->ocsp_response) + { + OCSP_RESPONSE_free(cbinfo->ocsp_response); + cbinfo->ocsp_response = NULL; + } + +bio = BIO_new_file(CS cbinfo->ocsp_file_expanded, "rb"); +if (!bio) + { + DEBUG(D_tls) debug_printf("Failed to open OCSP response file \"%s\"\n", + cbinfo->ocsp_file_expanded); + return; + } + +resp = d2i_OCSP_RESPONSE_bio(bio, NULL); +BIO_free(bio); +if (!resp) + { + DEBUG(D_tls) debug_printf("Error reading OCSP response.\n"); + return; + } + +status = OCSP_response_status(resp); +if (status != OCSP_RESPONSE_STATUS_SUCCESSFUL) + { + DEBUG(D_tls) debug_printf("OCSP response not valid: %s (%d)\n", + OCSP_response_status_str(status), status); + return; + } + +basic_response = OCSP_response_get1_basic(resp); +if (!basic_response) + { + DEBUG(D_tls) + debug_printf("OCSP response parse error: unable to extract basic response.\n"); + return; + } + +store = SSL_CTX_get_cert_store(sctx); +verify_flags = OCSP_NOVERIFY; /* check sigs, but not purpose */ + +/* May need to expose ability to adjust those flags? +OCSP_NOSIGS OCSP_NOVERIFY OCSP_NOCHAIN OCSP_NOCHECKS OCSP_NOEXPLICIT +OCSP_TRUSTOTHER OCSP_NOINTERN */ + +i = OCSP_basic_verify(basic_response, NULL, store, verify_flags); +if (i <= 0) + { + DEBUG(D_tls) { + ERR_error_string(ERR_get_error(), ssl_errstring); + debug_printf("OCSP response verify failure: %s\n", US ssl_errstring); + } + return; + } + +/* Here's the simplifying assumption: there's only one response, for the +one certificate we use, and nothing for anything else in a chain. If this +proves false, we need to extract a cert id from our issued cert +(tls_certificate) and use that for OCSP_resp_find_status() (which finds the +right cert in the stack and then calls OCSP_single_get0_status()). + +I'm hoping to avoid reworking a bunch more of how we handle state here. */ +single_response = OCSP_resp_get0(basic_response, 0); +if (!single_response) + { + DEBUG(D_tls) + debug_printf("Unable to get first response from OCSP basic response.\n"); + return; + } + +status = OCSP_single_get0_status(single_response, &reason, &rev, &thisupd, &nextupd); +/* how does this status differ from the one above? */ +if (status != OCSP_RESPONSE_STATUS_SUCCESSFUL) + { + DEBUG(D_tls) debug_printf("OCSP response not valid (take 2): %s (%d)\n", + OCSP_response_status_str(status), status); + return; + } + +if (!OCSP_check_validity(thisupd, nextupd, EXIM_OCSP_SKEW_SECONDS, EXIM_OCSP_MAX_AGE)) + { + DEBUG(D_tls) debug_printf("OCSP status invalid times.\n"); + return; + } + +cbinfo->ocsp_response = resp; +} +#endif + + + + +/************************************************* +* Expand key and cert file specs * +*************************************************/ + +/* Called once during tls_init and possibly againt during TLS setup, for a +new context, if Server Name Indication was used and tls_sni was seen in +the certificate string. + +Arguments: + sctx the SSL_CTX* to update + cbinfo various parts of session state + +Returns: OK/DEFER/FAIL +*/ + +static int +tls_expand_session_files(SSL_CTX *sctx, tls_ext_ctx_cb *cbinfo) +{ +uschar *expanded; + +if (cbinfo->certificate == NULL) + return OK; + +if (Ustrstr(cbinfo->certificate, US"tls_sni")) + reexpand_tls_files_for_sni = TRUE; + +if (!expand_check(cbinfo->certificate, US"tls_certificate", &expanded)) + return DEFER; + +if (expanded != NULL) + { + DEBUG(D_tls) debug_printf("tls_certificate file %s\n", expanded); + if (!SSL_CTX_use_certificate_chain_file(sctx, CS expanded)) + return tls_error(string_sprintf( + "SSL_CTX_use_certificate_chain_file file=%s", expanded), + cbinfo->host, NULL); + } + +if (cbinfo->privatekey != NULL && + !expand_check(cbinfo->privatekey, US"tls_privatekey", &expanded)) + return DEFER; + +/* If expansion was forced to fail, key_expanded will be NULL. If the result +of the expansion is an empty string, ignore it also, and assume the private +key is in the same file as the certificate. */ + +if (expanded != NULL && *expanded != 0) + { + DEBUG(D_tls) debug_printf("tls_privatekey file %s\n", expanded); + if (!SSL_CTX_use_PrivateKey_file(sctx, CS expanded, SSL_FILETYPE_PEM)) + return tls_error(string_sprintf( + "SSL_CTX_use_PrivateKey_file file=%s", expanded), cbinfo->host, NULL); + } + +#ifdef EXPERIMENTAL_OCSP +if (cbinfo->ocsp_file != NULL) + { + if (!expand_check(cbinfo->ocsp_file, US"tls_ocsp_file", &expanded)) + return DEFER; + + if (expanded != NULL && *expanded != 0) + { + DEBUG(D_tls) debug_printf("tls_ocsp_file %s\n", expanded); + if (cbinfo->ocsp_file_expanded && + (Ustrcmp(expanded, cbinfo->ocsp_file_expanded) == 0)) + { + DEBUG(D_tls) + debug_printf("tls_ocsp_file value unchanged, using existing values.\n"); + } else { + ocsp_load_response(sctx, cbinfo, expanded); + } + } + } +#endif + +return OK; +} + + + + +/************************************************* +* Callback to handle SNI * +*************************************************/ + +/* Called when acting as server during the TLS session setup if a Server Name +Indication extension was sent by the client. + +API documentation is OpenSSL s_server.c implementation. + +Arguments: + s SSL* of the current session + ad unknown (part of OpenSSL API) (unused) + arg Callback of "our" registered data + +Returns: SSL_TLSEXT_ERR_{OK,ALERT_WARNING,ALERT_FATAL,NOACK} +*/ + +#ifdef EXIM_HAVE_OPENSSL_TLSEXT +static int +tls_servername_cb(SSL *s, int *ad ARG_UNUSED, void *arg) +{ +const char *servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name); +tls_ext_ctx_cb *cbinfo = (tls_ext_ctx_cb *) arg; +int rc; +int old_pool = store_pool; + +if (!servername) + return SSL_TLSEXT_ERR_OK; + +DEBUG(D_tls) debug_printf("Received TLS SNI \"%s\"%s\n", servername, + reexpand_tls_files_for_sni ? "" : " (unused for certificate selection)"); + +/* Make the extension value available for expansion */ +store_pool = POOL_PERM; +tls_sni = string_copy(US servername); +store_pool = old_pool; + +if (!reexpand_tls_files_for_sni) + return SSL_TLSEXT_ERR_OK; + +/* Can't find an SSL_CTX_clone() or equivalent, so we do it manually; +not confident that memcpy wouldn't break some internal reference counting. +Especially since there's a references struct member, which would be off. */ + +ctx_sni = SSL_CTX_new(SSLv23_server_method()); +if (!ctx_sni) + { + ERR_error_string(ERR_get_error(), ssl_errstring); + DEBUG(D_tls) debug_printf("SSL_CTX_new() failed: %s\n", ssl_errstring); + return SSL_TLSEXT_ERR_NOACK; + } + +/* Not sure how many of these are actually needed, since SSL object +already exists. Might even need this selfsame callback, for reneg? */ + +SSL_CTX_set_info_callback(ctx_sni, SSL_CTX_get_info_callback(ctx)); +SSL_CTX_set_mode(ctx_sni, SSL_CTX_get_mode(ctx)); +SSL_CTX_set_options(ctx_sni, SSL_CTX_get_options(ctx)); +SSL_CTX_set_timeout(ctx_sni, SSL_CTX_get_timeout(ctx)); +SSL_CTX_set_tlsext_servername_callback(ctx_sni, tls_servername_cb); +SSL_CTX_set_tlsext_servername_arg(ctx_sni, cbinfo); +if (cbinfo->server_cipher_list) + SSL_CTX_set_cipher_list(ctx_sni, CS cbinfo->server_cipher_list); +#ifdef EXPERIMENTAL_OCSP +if (cbinfo->ocsp_file) + { + SSL_CTX_set_tlsext_status_cb(ctx_sni, tls_stapling_cb); + SSL_CTX_set_tlsext_status_arg(ctx, cbinfo); } +#endif + +rc = setup_certs(ctx_sni, tls_verify_certificates, tls_crl, NULL, FALSE); +if (rc != OK) return SSL_TLSEXT_ERR_NOACK; + +/* do this after setup_certs, because this can require the certs for verifying +OCSP information. */ +rc = tls_expand_session_files(ctx_sni, cbinfo); +if (rc != OK) return SSL_TLSEXT_ERR_NOACK; + +rc = init_dh(ctx_sni, cbinfo->dhparam, NULL); +if (rc != OK) return SSL_TLSEXT_ERR_NOACK; + +DEBUG(D_tls) debug_printf("Switching SSL context.\n"); +SSL_set_SSL_CTX(s, ctx_sni); + +return SSL_TLSEXT_ERR_OK; +} +#endif /* EXIM_HAVE_OPENSSL_TLSEXT */ + + + + +#ifdef EXPERIMENTAL_OCSP +/************************************************* +* Callback to handle OCSP Stapling * +*************************************************/ + +/* Called when acting as server during the TLS session setup if the client +requests OCSP information with a Certificate Status Request. + +Documentation via openssl s_server.c and the Apache patch from the OpenSSL +project. + +*/ -return yield; +static int +tls_stapling_cb(SSL *s, void *arg) +{ +const tls_ext_ctx_cb *cbinfo = (tls_ext_ctx_cb *) arg; +uschar *response_der; +int response_der_len; + +DEBUG(D_tls) debug_printf("Received TLS status request (OCSP stapling); %s response.\n", + cbinfo->ocsp_response ? "have" : "lack"); +if (!cbinfo->ocsp_response) + return SSL_TLSEXT_ERR_NOACK; + +response_der = NULL; +response_der_len = i2d_OCSP_RESPONSE(cbinfo->ocsp_response, &response_der); +if (response_der_len <= 0) + return SSL_TLSEXT_ERR_NOACK; + +SSL_set_tlsext_status_ocsp_resp(ssl, response_der, response_der_len); +return SSL_TLSEXT_ERR_OK; } +#endif /* EXPERIMENTAL_OCSP */ + @@ -300,10 +715,25 @@ Returns: OK/DEFER/FAIL static int tls_init(host_item *host, uschar *dhparam, uschar *certificate, - uschar *privatekey, address_item *addr) + uschar *privatekey, +#ifdef EXPERIMENTAL_OCSP + uschar *ocsp_file, +#endif + address_item *addr) { long init_options; +int rc; BOOL okay; +tls_ext_ctx_cb *cbinfo; + +cbinfo = store_malloc(sizeof(tls_ext_ctx_cb)); +cbinfo->certificate = certificate; +cbinfo->privatekey = privatekey; +#ifdef EXPERIMENTAL_OCSP +cbinfo->ocsp_file = ocsp_file; +#endif +cbinfo->dhparam = dhparam; +cbinfo->host = host; SSL_load_error_strings(); /* basic set up */ OpenSSL_add_ssl_algorithms(); @@ -349,6 +779,9 @@ level. */ SSL_CTX_set_info_callback(ctx, (void (*)())info_callback); +/* Automatically re-try reads/writes after renegotiation. */ +(void) SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY); + /* Apply administrator-supplied work-arounds. Historically we applied just one requested option, SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS, but when bug 994 requested a second, we @@ -360,7 +793,7 @@ availability of the option value macros from OpenSSL. */ okay = tls_openssl_options_parse(openssl_options, &init_options); if (!okay) - return tls_error("openssl_options parsing failed", host, NULL); + return tls_error(US"openssl_options parsing failed", host, NULL); if (init_options) { @@ -374,40 +807,34 @@ else /* Initialize with DH parameters if supplied */ -if (!init_dh(dhparam, host)) return DEFER; +if (!init_dh(ctx, dhparam, host)) return DEFER; -/* Set up certificate and key */ +/* Set up certificate and key (and perhaps OCSP info) */ -if (certificate != NULL) - { - uschar *expanded; - if (!expand_check(certificate, US"tls_certificate", &expanded)) - return DEFER; - - if (expanded != NULL) - { - DEBUG(D_tls) debug_printf("tls_certificate file %s\n", expanded); - if (!SSL_CTX_use_certificate_chain_file(ctx, CS expanded)) - return tls_error(string_sprintf( - "SSL_CTX_use_certificate_chain_file file=%s", expanded), host, NULL); - } - - if (privatekey != NULL && - !expand_check(privatekey, US"tls_privatekey", &expanded)) - return DEFER; - - /* If expansion was forced to fail, key_expanded will be NULL. If the result - of the expansion is an empty string, ignore it also, and assume the private - key is in the same file as the certificate. */ +rc = tls_expand_session_files(ctx, cbinfo); +if (rc != OK) return rc; - if (expanded != NULL && *expanded != 0) +/* If we need to handle SNI, do so */ +#ifdef EXIM_HAVE_OPENSSL_TLSEXT +if (host == NULL) + { +#ifdef EXPERIMENTAL_OCSP + /* We check ocsp_file, not ocsp_response, because we care about if + the option exists, not what the current expansion might be, as SNI might + change the certificate and OCSP file in use between now and the time the + callback is invoked. */ + if (cbinfo->ocsp_file) { - DEBUG(D_tls) debug_printf("tls_privatekey file %s\n", expanded); - if (!SSL_CTX_use_PrivateKey_file(ctx, CS expanded, SSL_FILETYPE_PEM)) - return tls_error(string_sprintf( - "SSL_CTX_use_PrivateKey_file file=%s", expanded), host, NULL); + SSL_CTX_set_tlsext_status_cb(ctx, tls_stapling_cb); + SSL_CTX_set_tlsext_status_arg(ctx, cbinfo); } +#endif + /* We always do this, so that $tls_sni is available even if not used in + tls_certificate */ + SSL_CTX_set_tlsext_servername_callback(ctx, tls_servername_cb); + SSL_CTX_set_tlsext_servername_arg(ctx, cbinfo); } +#endif /* Set up the RSA callback */ @@ -417,6 +844,9 @@ SSL_CTX_set_tmp_rsa_callback(ctx, rsa_callback); SSL_CTX_set_timeout(ctx, ssl_session_timeout); DEBUG(D_tls) debug_printf("Initialized TLS\n"); + +static_cbinfo = cbinfo; + return OK; } @@ -438,9 +868,11 @@ static void construct_cipher_name(SSL *ssl) { static uschar cipherbuf[256]; -SSL_CIPHER *c; +/* With OpenSSL 1.0.0a, this needs to be const but the documentation doesn't +yet reflect that. It should be a safe change anyway, even 0.9.8 versions have +the accessor functions use const in the prototype. */ +const SSL_CIPHER *c; uschar *ver; -int bits; switch (ssl->session->ssl_version) { @@ -456,15 +888,27 @@ switch (ssl->session->ssl_version) ver = US"TLSv1"; break; +#ifdef TLS1_1_VERSION + case TLS1_1_VERSION: + ver = US"TLSv1.1"; + break; +#endif + +#ifdef TLS1_2_VERSION + case TLS1_2_VERSION: + ver = US"TLSv1.2"; + break; +#endif + default: ver = US"UNKNOWN"; } -c = SSL_get_current_cipher(ssl); -SSL_CIPHER_get_bits(c, &bits); +c = (const SSL_CIPHER *) SSL_get_current_cipher(ssl); +SSL_CIPHER_get_bits(c, &tls_bits); string_format(cipherbuf, sizeof(cipherbuf), "%s:%s:%u", ver, - SSL_CIPHER_get_name(c), bits); + SSL_CIPHER_get_name(c), tls_bits); tls_cipher = cipherbuf; DEBUG(D_tls) debug_printf("Cipher: %s\n", cipherbuf); @@ -481,6 +925,7 @@ DEBUG(D_tls) debug_printf("Cipher: %s\n", cipherbuf); /* Called by both client and server startup Arguments: + sctx SSL_CTX* to initialise certs certs file or NULL crl CRL file or NULL host NULL in a server; the remote host in a client @@ -491,7 +936,7 @@ Returns: OK/DEFER/FAIL */ static int -setup_certs(uschar *certs, uschar *crl, host_item *host, BOOL optional) +setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional) { uschar *expcerts, *expcrl; @@ -501,7 +946,7 @@ if (!expand_check(certs, US"tls_verify_certificates", &expcerts)) if (expcerts != NULL) { struct stat statbuf; - if (!SSL_CTX_set_default_verify_paths(ctx)) + if (!SSL_CTX_set_default_verify_paths(sctx)) return tls_error(US"SSL_CTX_set_default_verify_paths", host, NULL); if (Ustat(expcerts, &statbuf) < 0) @@ -524,12 +969,12 @@ if (expcerts != NULL) says no certificate was supplied.) But this is better. */ if ((file == NULL || statbuf.st_size > 0) && - !SSL_CTX_load_verify_locations(ctx, CS file, CS dir)) + !SSL_CTX_load_verify_locations(sctx, CS file, CS dir)) return tls_error(US"SSL_CTX_load_verify_locations", host, NULL); if (file != NULL) { - SSL_CTX_set_client_CA_list(ctx, SSL_load_client_CA_file(CS file)); + SSL_CTX_set_client_CA_list(sctx, SSL_load_client_CA_file(CS file)); } } @@ -561,7 +1006,7 @@ if (expcerts != NULL) { /* is it a file or directory? */ uschar *file, *dir; - X509_STORE *cvstore = SSL_CTX_get_cert_store(ctx); + X509_STORE *cvstore = SSL_CTX_get_cert_store(sctx); if ((statbufcrl.st_mode & S_IFMT) == S_IFDIR) { file = NULL; @@ -588,7 +1033,7 @@ if (expcerts != NULL) /* If verification is optional, don't fail if no certificate */ - SSL_CTX_set_verify(ctx, + SSL_CTX_set_verify(sctx, SSL_VERIFY_PEER | (optional? 0 : SSL_VERIFY_FAIL_IF_NO_PEER_CERT), verify_callback); } @@ -608,11 +1053,6 @@ a TLS session. Arguments: require_ciphers allowed ciphers - ------------------------------------------------------ - require_mac list of allowed MACs ) Not used - require_kx list of allowed key_exchange methods ) for - require_proto list of allowed protocols ) OpenSSL - ------------------------------------------------------ Returns: OK on success DEFER for errors before the start of the negotiation @@ -621,11 +1061,11 @@ Returns: OK on success */ int -tls_server_start(uschar *require_ciphers, uschar *require_mac, - uschar *require_kx, uschar *require_proto) +tls_server_start(const uschar *require_ciphers) { int rc; uschar *expciphers; +tls_ext_ctx_cb *cbinfo; /* Check for previous activation */ @@ -639,15 +1079,21 @@ if (tls_active >= 0) /* Initialize the SSL library. If it fails, it will already have logged the error. */ -rc = tls_init(NULL, tls_dhparam, tls_certificate, tls_privatekey, NULL); +rc = tls_init(NULL, tls_dhparam, tls_certificate, tls_privatekey, +#ifdef EXPERIMENTAL_OCSP + tls_ocsp_file, +#endif + NULL); if (rc != OK) return rc; +cbinfo = static_cbinfo; if (!expand_check(require_ciphers, US"tls_require_ciphers", &expciphers)) return FAIL; /* In OpenSSL, cipher components are separated by hyphens. In GnuTLS, they -are separated by underscores. So that I can use either form in my tests, and -also for general convenience, we turn underscores into hyphens here. */ +were historically separated by underscores. So that I can use either form in my +tests, and also for general convenience, we turn underscores into hyphens here. +*/ if (expciphers != NULL) { @@ -656,6 +1102,7 @@ if (expciphers != NULL) DEBUG(D_tls) debug_printf("required ciphers: %s\n", expciphers); if (!SSL_CTX_set_cipher_list(ctx, CS expciphers)) return tls_error(US"SSL_CTX_set_cipher_list", NULL, NULL); + cbinfo->server_cipher_list = expciphers; } /* If this is a host for which certificate verification is mandatory or @@ -666,13 +1113,13 @@ verify_callback_called = FALSE; if (verify_check_host(&tls_verify_hosts) == OK) { - rc = setup_certs(tls_verify_certificates, tls_crl, NULL, FALSE); + rc = setup_certs(ctx, tls_verify_certificates, tls_crl, NULL, FALSE); if (rc != OK) return rc; verify_optional = FALSE; } else if (verify_check_host(&tls_try_verify_hosts) == OK) { - rc = setup_certs(tls_verify_certificates, tls_crl, NULL, TRUE); + rc = setup_certs(ctx, tls_verify_certificates, tls_crl, NULL, TRUE); if (rc != OK) return rc; verify_optional = TRUE; } @@ -680,7 +1127,19 @@ else if (verify_check_host(&tls_try_verify_hosts) == OK) /* Prepare for new connection */ if ((ssl = SSL_new(ctx)) == NULL) return tls_error(US"SSL_new", NULL, NULL); -SSL_clear(ssl); + +/* Warning: we used to SSL_clear(ssl) here, it was removed. + * + * With the SSL_clear(), we get strange interoperability bugs with + * OpenSSL 1.0.1b and TLS1.1/1.2. It looks as though this may be a bug in + * OpenSSL itself, as a clear should not lead to inability to follow protocols. + * + * The SSL_clear() call is to let an existing SSL* be reused, typically after + * session shutdown. In this case, we have a brand new object and there's no + * obvious reason to immediately clear it. I'm guessing that this was + * originally added because of incomplete initialisation which the clear fixed, + * in some historic release. + */ /* Set context and tell client to go ahead, except in the case of TLS startup on connection, where outputting anything now upsets the clients and tends to @@ -714,7 +1173,7 @@ if (rc <= 0) tls_error(US"SSL_accept", NULL, sigalrm_seen ? US"timed out" : NULL); if (ERR_get_error() == 0) log_write(0, LOG_MAIN, - " => client disconnected cleanly (rejected our certificate?)\n"); + "TLS client disconnected cleanly (rejected our certificate?)"); return FAIL; } @@ -764,14 +1223,10 @@ Argument: dhparam DH parameter file certificate certificate file privatekey private key file + sni TLS SNI to send to remote host verify_certs file for certificate verify crl file containing CRL require_ciphers list of allowed ciphers - ------------------------------------------------------ - require_mac list of allowed MACs ) Not used - require_kx list of allowed key_exchange methods ) for - require_proto list of allowed protocols ) OpenSSL - ------------------------------------------------------ timeout startup timeout Returns: OK on success @@ -781,16 +1236,20 @@ Returns: OK on success int tls_client_start(int fd, host_item *host, address_item *addr, uschar *dhparam, - uschar *certificate, uschar *privatekey, uschar *verify_certs, uschar *crl, - uschar *require_ciphers, uschar *require_mac, uschar *require_kx, - uschar *require_proto, int timeout) + uschar *certificate, uschar *privatekey, uschar *sni, + uschar *verify_certs, uschar *crl, + uschar *require_ciphers, int timeout) { static uschar txt[256]; uschar *expciphers; X509* server_cert; int rc; -rc = tls_init(host, dhparam, certificate, privatekey, addr); +rc = tls_init(host, dhparam, certificate, privatekey, +#ifdef EXPERIMENTAL_OCSP + NULL, +#endif + addr); if (rc != OK) return rc; tls_certificate_verified = FALSE; @@ -812,7 +1271,7 @@ if (expciphers != NULL) return tls_error(US"SSL_CTX_set_cipher_list", host, NULL); } -rc = setup_certs(verify_certs, crl, host, FALSE); +rc = setup_certs(ctx, verify_certs, crl, host, FALSE); if (rc != OK) return rc; if ((ssl = SSL_new(ctx)) == NULL) return tls_error(US"SSL_new", host, NULL); @@ -820,6 +1279,25 @@ SSL_set_session_id_context(ssl, sid_ctx, Ustrlen(sid_ctx)); SSL_set_fd(ssl, fd); SSL_set_connect_state(ssl); +if (sni) + { + if (!expand_check(sni, US"tls_sni", &tls_sni)) + return FAIL; + if (!Ustrlen(tls_sni)) + tls_sni = NULL; + else + { +#ifdef EXIM_HAVE_OPENSSL_TLSEXT + DEBUG(D_tls) debug_printf("Setting TLS SNI \"%s\"\n", tls_sni); + SSL_set_tlsext_host_name(ssl, tls_sni); +#else + DEBUG(D_tls) + debug_printf("OpenSSL at build-time lacked SNI support, ignoring \"%s\"\n", + tls_sni); +#endif + } + } + /* There doesn't seem to be a built-in timeout on connection. */ DEBUG(D_tls) debug_printf("Calling SSL_connect\n"); @@ -833,10 +1311,16 @@ if (rc <= 0) DEBUG(D_tls) debug_printf("SSL_connect succeeded\n"); +/* Beware anonymous ciphers which lead to server_cert being NULL */ server_cert = SSL_get_peer_certificate (ssl); -tls_peerdn = US X509_NAME_oneline(X509_get_subject_name(server_cert), - CS txt, sizeof(txt)); -tls_peerdn = txt; +if (server_cert) + { + tls_peerdn = US X509_NAME_oneline(X509_get_subject_name(server_cert), + CS txt, sizeof(txt)); + tls_peerdn = txt; + } +else + tls_peerdn = NULL; construct_cipher_name(ssl); /* Sets tls_cipher */ @@ -867,8 +1351,8 @@ if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm) int error; int inbytes; - DEBUG(D_tls) debug_printf("Calling SSL_read(%lx, %lx, %u)\n", (long)ssl, - (long)ssl_xfer_buffer, ssl_xfer_buffer_size); + DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", ssl, + ssl_xfer_buffer, ssl_xfer_buffer_size); if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout); inbytes = SSL_read(ssl, CS ssl_xfer_buffer, ssl_xfer_buffer_size); @@ -892,8 +1376,10 @@ if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm) SSL_free(ssl); ssl = NULL; tls_active = -1; + tls_bits = 0; tls_cipher = NULL; tls_peerdn = NULL; + tls_sni = NULL; return smtp_getc(); } @@ -903,7 +1389,7 @@ if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm) else if (error == SSL_ERROR_SSL) { ERR_error_string(ERR_get_error(), ssl_errstring); - log_write(0, LOG_MAIN, "TLS error (SSL_write): %s", ssl_errstring); + log_write(0, LOG_MAIN, "TLS error (SSL_read): %s", ssl_errstring); ssl_xfer_error = 1; return EOF; } @@ -914,6 +1400,7 @@ if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm) ssl_xfer_error = 1; return EOF; } + #ifndef DISABLE_DKIM dkim_exim_verify_feed(ssl_xfer_buffer, inbytes); #endif @@ -947,8 +1434,8 @@ tls_read(uschar *buff, size_t len) int inbytes; int error; -DEBUG(D_tls) debug_printf("Calling SSL_read(%lx, %lx, %u)\n", (long)ssl, - (long)buff, (unsigned int)len); +DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", ssl, + buff, (unsigned int)len); inbytes = SSL_read(ssl, CS buff, len); error = SSL_get_error(ssl, inbytes); @@ -990,10 +1477,10 @@ int outbytes; int error; int left = len; -DEBUG(D_tls) debug_printf("tls_do_write(%lx, %d)\n", (long)buff, left); +DEBUG(D_tls) debug_printf("tls_do_write(%p, %d)\n", buff, left); while (left > 0) { - DEBUG(D_tls) debug_printf("SSL_write(SSL, %lx, %d)\n", (long)buff, left); + DEBUG(D_tls) debug_printf("SSL_write(SSL, %p, %d)\n", buff, left); outbytes = SSL_write(ssl, CS buff, left); error = SSL_get_error(ssl, outbytes); DEBUG(D_tls) debug_printf("outbytes=%d error=%d\n", outbytes, error); @@ -1055,6 +1542,72 @@ tls_active = -1; +/************************************************* +* Let tls_require_ciphers be checked at startup * +*************************************************/ + +/* The tls_require_ciphers option, if set, must be something which the +library can parse. + +Returns: NULL on success, or error message +*/ + +uschar * +tls_validate_require_cipher(void) +{ +SSL_CTX *ctx; +uschar *s, *expciphers, *err; + +/* this duplicates from tls_init(), we need a better "init just global +state, for no specific purpose" singleton function of our own */ + +SSL_load_error_strings(); +OpenSSL_add_ssl_algorithms(); +#if (OPENSSL_VERSION_NUMBER >= 0x0090800fL) && !defined(OPENSSL_NO_SHA256) +/* SHA256 is becoming ever more popular. This makes sure it gets added to the +list of available digests. */ +EVP_add_digest(EVP_sha256()); +#endif + +if (!(tls_require_ciphers && *tls_require_ciphers)) + return NULL; + +if (!expand_check(tls_require_ciphers, US"tls_require_ciphers", &expciphers)) + return US"failed to expand tls_require_ciphers"; + +if (!(expciphers && *expciphers)) + return NULL; + +/* normalisation ripped from above */ +s = expciphers; +while (*s != 0) { if (*s == '_') *s = '-'; s++; } + +err = NULL; + +ctx = SSL_CTX_new(SSLv23_server_method()); +if (!ctx) + { + ERR_error_string(ERR_get_error(), ssl_errstring); + return string_sprintf("SSL_CTX_new() failed: %s", ssl_errstring); + } + +DEBUG(D_tls) + debug_printf("tls_require_ciphers expands to \"%s\"\n", expciphers); + +if (!SSL_CTX_set_cipher_list(ctx, CS expciphers)) + { + ERR_error_string(ERR_get_error(), ssl_errstring); + err = string_sprintf("SSL_CTX_set_cipher_list(%s) failed", expciphers); + } + +SSL_CTX_free(ctx); + +return err; +} + + + + /************************************************* * Report the library versions. * *************************************************/ @@ -1072,15 +1625,17 @@ Returns: nothing void tls_version_report(FILE *f) { -fprintf(f, "OpenSSL compile-time version: %s\n", OPENSSL_VERSION_TEXT); -fprintf(f, "OpenSSL runtime version: %s\n", SSLeay_version(SSLEAY_VERSION)); +fprintf(f, "Library version: OpenSSL: Compile: %s\n" + " Runtime: %s\n", + OPENSSL_VERSION_TEXT, + SSLeay_version(SSLEAY_VERSION)); } /************************************************* -* Pseudo-random number generation * +* Random number generation * *************************************************/ /* Pseudo-random number generation. The result is not expected to be @@ -1095,7 +1650,7 @@ Returns a random number in range [0, max-1] */ int -pseudo_random_number(int max) +vaguely_random_number(int max) { unsigned int r; int i, needed_len; @@ -1131,7 +1686,14 @@ if (i < needed_len) needed_len = i; /* We do not care if crypto-strong */ -(void) RAND_pseudo_bytes(smallbuf, needed_len); +i = RAND_pseudo_bytes(smallbuf, needed_len); +if (i < 0) + { + DEBUG(D_all) + debug_printf("OpenSSL RAND_pseudo_bytes() not supported by RAND method, using fallback.\n"); + return vaguely_random_number_fallback(max); + } + r = 0; for (p = smallbuf; needed_len; --needed_len, ++p) { @@ -1169,70 +1731,97 @@ all options unless explicitly for DTLS, let the administrator choose which to apply. This list is current as of: - ==> 0.9.8n <== */ + ==> 1.0.1b <== */ static struct exim_openssl_option exim_openssl_options[] = { /* KEEP SORTED ALPHABETICALLY! */ #ifdef SSL_OP_ALL - { "all", SSL_OP_ALL }, + { US"all", SSL_OP_ALL }, #endif #ifdef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION - { "allow_unsafe_legacy_renegotiation", SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION }, + { US"allow_unsafe_legacy_renegotiation", SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION }, #endif #ifdef SSL_OP_CIPHER_SERVER_PREFERENCE - { "cipher_server_preference", SSL_OP_CIPHER_SERVER_PREFERENCE }, + { US"cipher_server_preference", SSL_OP_CIPHER_SERVER_PREFERENCE }, #endif #ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS - { "dont_insert_empty_fragments", SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS }, + { US"dont_insert_empty_fragments", SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS }, #endif #ifdef SSL_OP_EPHEMERAL_RSA - { "ephemeral_rsa", SSL_OP_EPHEMERAL_RSA }, + { US"ephemeral_rsa", SSL_OP_EPHEMERAL_RSA }, #endif #ifdef SSL_OP_LEGACY_SERVER_CONNECT - { "legacy_server_connect", SSL_OP_LEGACY_SERVER_CONNECT }, + { US"legacy_server_connect", SSL_OP_LEGACY_SERVER_CONNECT }, #endif #ifdef SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER - { "microsoft_big_sslv3_buffer", SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER }, + { US"microsoft_big_sslv3_buffer", SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER }, #endif #ifdef SSL_OP_MICROSOFT_SESS_ID_BUG - { "microsoft_sess_id_bug", SSL_OP_MICROSOFT_SESS_ID_BUG }, + { US"microsoft_sess_id_bug", SSL_OP_MICROSOFT_SESS_ID_BUG }, #endif #ifdef SSL_OP_MSIE_SSLV2_RSA_PADDING - { "msie_sslv2_rsa_padding", SSL_OP_MSIE_SSLV2_RSA_PADDING }, + { US"msie_sslv2_rsa_padding", SSL_OP_MSIE_SSLV2_RSA_PADDING }, #endif #ifdef SSL_OP_NETSCAPE_CHALLENGE_BUG - { "netscape_challenge_bug", SSL_OP_NETSCAPE_CHALLENGE_BUG }, + { US"netscape_challenge_bug", SSL_OP_NETSCAPE_CHALLENGE_BUG }, #endif #ifdef SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG - { "netscape_reuse_cipher_change_bug", SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG }, + { US"netscape_reuse_cipher_change_bug", SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG }, +#endif +#ifdef SSL_OP_NO_COMPRESSION + { US"no_compression", SSL_OP_NO_COMPRESSION }, #endif #ifdef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION - { "no_session_resumption_on_renegotiation", SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION }, + { US"no_session_resumption_on_renegotiation", SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION }, +#endif +#ifdef SSL_OP_NO_SSLv2 + { US"no_sslv2", SSL_OP_NO_SSLv2 }, +#endif +#ifdef SSL_OP_NO_SSLv3 + { US"no_sslv3", SSL_OP_NO_SSLv3 }, +#endif +#ifdef SSL_OP_NO_TICKET + { US"no_ticket", SSL_OP_NO_TICKET }, +#endif +#ifdef SSL_OP_NO_TLSv1 + { US"no_tlsv1", SSL_OP_NO_TLSv1 }, +#endif +#ifdef SSL_OP_NO_TLSv1_1 +#if SSL_OP_NO_TLSv1_1 == 0x00000400L + /* Error in chosen value in 1.0.1a; see first item in CHANGES for 1.0.1b */ +#warning OpenSSL 1.0.1a uses a bad value for SSL_OP_NO_TLSv1_1, ignoring +#else + { US"no_tlsv1_1", SSL_OP_NO_TLSv1_1 }, +#endif +#endif +#ifdef SSL_OP_NO_TLSv1_2 + { US"no_tlsv1_2", SSL_OP_NO_TLSv1_2 }, #endif #ifdef SSL_OP_SINGLE_DH_USE - { "single_dh_use", SSL_OP_SINGLE_DH_USE }, + { US"single_dh_use", SSL_OP_SINGLE_DH_USE }, #endif #ifdef SSL_OP_SINGLE_ECDH_USE - { "single_ecdh_use", SSL_OP_SINGLE_ECDH_USE }, + { US"single_ecdh_use", SSL_OP_SINGLE_ECDH_USE }, #endif #ifdef SSL_OP_SSLEAY_080_CLIENT_DH_BUG - { "ssleay_080_client_dh_bug", SSL_OP_SSLEAY_080_CLIENT_DH_BUG }, + { US"ssleay_080_client_dh_bug", SSL_OP_SSLEAY_080_CLIENT_DH_BUG }, #endif #ifdef SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG - { "sslref2_reuse_cert_type_bug", SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG }, + { US"sslref2_reuse_cert_type_bug", SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG }, #endif #ifdef SSL_OP_TLS_BLOCK_PADDING_BUG - { "tls_block_padding_bug", SSL_OP_TLS_BLOCK_PADDING_BUG }, + { US"tls_block_padding_bug", SSL_OP_TLS_BLOCK_PADDING_BUG }, #endif #ifdef SSL_OP_TLS_D5_BUG - { "tls_d5_bug", SSL_OP_TLS_D5_BUG }, + { US"tls_d5_bug", SSL_OP_TLS_D5_BUG }, #endif #ifdef SSL_OP_TLS_ROLLBACK_BUG - { "tls_rollback_bug", SSL_OP_TLS_ROLLBACK_BUG }, + { US"tls_rollback_bug", SSL_OP_TLS_ROLLBACK_BUG }, #endif }; static int exim_openssl_options_size = sizeof(exim_openssl_options)/sizeof(struct exim_openssl_option); + static BOOL tls_openssl_one_option_parse(uschar *name, long *value) { @@ -1280,12 +1869,9 @@ uschar *s, *end; uschar keep_c; BOOL adding, item_parsed; -/* We grandfather in as default the one option which we used to set always. */ -#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS -result = SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; -#else result = 0L; -#endif +/* Prior to 4.80 we or'd in SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; removed + * from default because it increases BEAST susceptibility. */ if (option_spec == NULL) { @@ -1301,7 +1887,7 @@ for (s=option_spec; *s != '\0'; /**/) if (*s != '+' && *s != '-') { DEBUG(D_tls) debug_printf("malformed openssl option setting: " - "+ or - expected but found \"%s\"", s); + "+ or - expected but found \"%s\"\n", s); return FALSE; } adding = *s++ == '+'; @@ -1311,7 +1897,7 @@ for (s=option_spec; *s != '\0'; /**/) item_parsed = tls_openssl_one_option_parse(s, &item); if (!item_parsed) { - DEBUG(D_tls) debug_printf("openssl option setting unrecognised: \"%s\"", s); + DEBUG(D_tls) debug_printf("openssl option setting unrecognised: \"%s\"\n", s); return FALSE; } DEBUG(D_tls) debug_printf("openssl option, %s from %lx: %lx (%s)\n",