X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/f0f5a555bee153477d12bcbce90875d46884281c..de6135a0cbbeb4fbae7233a40563a241de1c237b:/src/src/tls-openssl.c diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index ea32bdb40..18cb787a5 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -2,7 +2,7 @@ * 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 @@ -20,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. */ @@ -30,17 +42,43 @@ typedef struct randstuff { /* Local static variables */ -static BOOL verify_callback_called = FALSE; +static BOOL client_verify_callback_called = FALSE; +static BOOL server_verify_callback_called = FALSE; static const uschar *sid_ctx = US"exim"; -static SSL_CTX *ctx = NULL; -static SSL_CTX *ctx_sni = NULL; -static SSL *ssl = NULL; +/* We have three different contexts to care about. + +Simple case: client, `client_ctx` + As a client, we can be doing a callout or cut-through delivery while receiving + a message. So we have a client context, which should have options initialised + from the SMTP Transport. + +Server: + There are two cases: with and without ServerNameIndication from the client. + Given TLS SNI, we can be using different keys, certs and various other + configuration settings, because they're re-expanded with $tls_sni set. This + allows vhosting with TLS. This SNI is sent in the handshake. + A client might not send SNI, so we need a fallback, and an initial setup too. + So as a server, we start out using `server_ctx`. + If SNI is sent by the client, then we as server, mid-negotiation, try to clone + `server_sni` from `server_ctx` and then initialise settings by re-expanding + configuration. +*/ + +static SSL_CTX *client_ctx = NULL; +static SSL_CTX *server_ctx = NULL; +static SSL *client_ssl = NULL; +static SSL *server_ssl = NULL; + +#ifdef EXIM_HAVE_OPENSSL_TLSEXT +static SSL_CTX *server_sni = NULL; +#endif static char ssl_errstring[256]; static int ssl_session_timeout = 200; -static BOOL verify_optional = FALSE; +static BOOL client_verify_optional = FALSE; +static BOOL server_verify_optional = FALSE; static BOOL reexpand_tls_files_for_sni = FALSE; @@ -48,6 +86,11 @@ 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; @@ -58,10 +101,19 @@ typedef struct 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; +tls_ext_ctx_cb *client_static_cbinfo = NULL; +tls_ext_ctx_cb *server_static_cbinfo = NULL; static int -setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional); +setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional, BOOL client); + +/* 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 /************************************************* @@ -169,14 +221,31 @@ setting SSL_VERIFY_FAIL_IF_NO_PEER_CERT in the non-optional case. Arguments: state current yes/no state as 1/0 x509ctx certificate information. + client TRUE for client startup, FALSE for server startup Returns: 1 if verified, 0 if not */ static int -verify_callback(int state, X509_STORE_CTX *x509ctx) +verify_callback(int state, X509_STORE_CTX *x509ctx, BOOL client) { static uschar txt[256]; +tls_support * tlsp; +BOOL * calledp; +BOOL * optionalp; + +if (client) + { + tlsp= &tls_out; + calledp= &client_verify_callback_called; + optionalp= &client_verify_optional; + } +else + { + tlsp= &tls_in; + calledp= &server_verify_callback_called; + optionalp= &server_verify_optional; + } X509_NAME_oneline(X509_get_subject_name(x509ctx->current_cert), CS txt, sizeof(txt)); @@ -187,9 +256,9 @@ if (state == 0) x509ctx->error_depth, X509_verify_cert_error_string(x509ctx->error), txt); - tls_certificate_verified = FALSE; - verify_callback_called = TRUE; - if (!verify_optional) return 0; /* reject */ + tlsp->certificate_verified = FALSE; + *calledp = TRUE; + if (!*optionalp) return 0; /* reject */ DEBUG(D_tls) debug_printf("SSL verify failure overridden (host in " "tls_try_verify_hosts)\n"); return 1; /* accept */ @@ -203,16 +272,28 @@ if (x509ctx->error_depth != 0) else { DEBUG(D_tls) debug_printf("SSL%s peer: %s\n", - verify_callback_called? "" : " authenticated", txt); - tls_peerdn = txt; + *calledp ? "" : " authenticated", txt); + tlsp->peerdn = txt; } -if (!verify_callback_called) tls_certificate_verified = TRUE; -verify_callback_called = TRUE; +if (!*calledp) tlsp->certificate_verified = TRUE; +*calledp = TRUE; return 1; /* accept */ } +static int +verify_callback_client(int state, X509_STORE_CTX *x509ctx) +{ +return verify_callback(state, x509ctx, TRUE); +} + +static int +verify_callback_server(int state, X509_STORE_CTX *x509ctx) +{ +return verify_callback(state, x509ctx, FALSE); +} + /************************************************* @@ -248,52 +329,211 @@ 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; } -return yield; +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 @@ -314,14 +554,17 @@ Returns: OK/DEFER/FAIL */ static int -tls_expand_session_files(SSL_CTX *sctx, const tls_ext_ctx_cb *cbinfo) +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")) +if (Ustrstr(cbinfo->certificate, US"tls_sni") || + Ustrstr(cbinfo->certificate, US"tls_in_sni") || + Ustrstr(cbinfo->certificate, US"tls_out_sni") + ) reexpand_tls_files_for_sni = TRUE; if (!expand_check(cbinfo->certificate, US"tls_certificate", &expanded)) @@ -352,6 +595,27 @@ if (expanded != NULL && *expanded != 0) "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; } @@ -375,15 +639,12 @@ Arguments: Returns: SSL_TLSEXT_ERR_{OK,ALERT_WARNING,ALERT_FATAL,NOACK} */ -static int -tls_servername_cb(SSL *s, int *ad ARG_UNUSED, void *arg); -/* pre-declared for SSL_CTX_set_tlsext_servername_callback call within func */ - +#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); -const tls_ext_ctx_cb *cbinfo = (tls_ext_ctx_cb *) arg; +tls_ext_ctx_cb *cbinfo = (tls_ext_ctx_cb *) arg; int rc; int old_pool = store_pool; @@ -395,7 +656,7 @@ DEBUG(D_tls) debug_printf("Received TLS SNI \"%s\"%s\n", servername, /* Make the extension value available for expansion */ store_pool = POOL_PERM; -tls_sni = string_copy(US servername); +tls_in.sni = string_copy(US servername); store_pool = old_pool; if (!reexpand_tls_files_for_sni) @@ -405,8 +666,8 @@ if (!reexpand_tls_files_for_sni) 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) +server_sni = SSL_CTX_new(SSLv23_server_method()); +if (!server_sni) { ERR_error_string(ERR_get_error(), ssl_errstring); DEBUG(D_tls) debug_printf("SSL_CTX_new() failed: %s\n", ssl_errstring); @@ -416,26 +677,78 @@ if (!ctx_sni) /* 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); +SSL_CTX_set_info_callback(server_sni, SSL_CTX_get_info_callback(server_ctx)); +SSL_CTX_set_mode(server_sni, SSL_CTX_get_mode(server_ctx)); +SSL_CTX_set_options(server_sni, SSL_CTX_get_options(server_ctx)); +SSL_CTX_set_timeout(server_sni, SSL_CTX_get_timeout(server_ctx)); +SSL_CTX_set_tlsext_servername_callback(server_sni, tls_servername_cb); +SSL_CTX_set_tlsext_servername_arg(server_sni, cbinfo); if (cbinfo->server_cipher_list) - SSL_CTX_set_cipher_list(ctx_sni, CS cbinfo->server_cipher_list); + SSL_CTX_set_cipher_list(server_sni, CS cbinfo->server_cipher_list); +#ifdef EXPERIMENTAL_OCSP +if (cbinfo->ocsp_file) + { + SSL_CTX_set_tlsext_status_cb(server_sni, tls_stapling_cb); + SSL_CTX_set_tlsext_status_arg(server_sni, cbinfo); + } +#endif + +rc = setup_certs(server_sni, tls_verify_certificates, tls_crl, NULL, FALSE, FALSE); +if (rc != OK) return SSL_TLSEXT_ERR_NOACK; -rc = tls_expand_session_files(ctx_sni, cbinfo); +/* do this after setup_certs, because this can require the certs for verifying +OCSP information. */ +rc = tls_expand_session_files(server_sni, cbinfo); if (rc != OK) return SSL_TLSEXT_ERR_NOACK; -rc = setup_certs(ctx_sni, tls_verify_certificates, tls_crl, NULL, FALSE); +rc = init_dh(server_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); +SSL_set_SSL_CTX(s, server_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. + +*/ + +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(server_ssl, response_der, response_der_len); +return SSL_TLSEXT_ERR_OK; +} + +#endif /* EXPERIMENTAL_OCSP */ @@ -458,8 +771,12 @@ Returns: OK/DEFER/FAIL */ static int -tls_init(host_item *host, uschar *dhparam, uschar *certificate, - uschar *privatekey, address_item *addr) +tls_init(SSL_CTX **ctxp, host_item *host, uschar *dhparam, uschar *certificate, + uschar *privatekey, +#ifdef EXPERIMENTAL_OCSP + uschar *ocsp_file, +#endif + address_item *addr, tls_ext_ctx_cb ** cbp) { long init_options; int rc; @@ -469,6 +786,11 @@ 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; +cbinfo->ocsp_file_expanded = NULL; +cbinfo->ocsp_response = NULL; +#endif cbinfo->dhparam = dhparam; cbinfo->host = host; @@ -489,10 +811,10 @@ when OpenSSL is built without SSLv2 support. By disabling with openssl_options, we can let admins re-enable with the existing knob. */ -ctx = SSL_CTX_new((host == NULL)? +*ctxp = SSL_CTX_new((host == NULL)? SSLv23_server_method() : SSLv23_client_method()); -if (ctx == NULL) return tls_error(US"SSL_CTX_new", host, NULL); +if (*ctxp == NULL) return tls_error(US"SSL_CTX_new", host, NULL); /* It turns out that we need to seed the random number generator this early in order to get the full complement of ciphers to work. It took me roughly a day @@ -520,10 +842,10 @@ if (!RAND_status()) /* Set up the information callback, which outputs if debugging is at a suitable level. */ -SSL_CTX_set_info_callback(ctx, (void (*)())info_callback); +SSL_CTX_set_info_callback(*ctxp, (void (*)())info_callback); /* Automatically re-try reads/writes after renegotiation. */ -(void) SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY); +(void) SSL_CTX_set_mode(*ctxp, SSL_MODE_AUTO_RETRY); /* Apply administrator-supplied work-arounds. Historically we applied just one requested option, @@ -541,7 +863,7 @@ if (!okay) if (init_options) { DEBUG(D_tls) debug_printf("setting SSL CTX options: %#lx\n", init_options); - if (!(SSL_CTX_set_options(ctx, init_options))) + if (!(SSL_CTX_set_options(*ctxp, init_options))) return tls_error(string_sprintf( "SSL_CTX_set_option(%#lx)", init_options), host, NULL); } @@ -550,34 +872,45 @@ else /* Initialize with DH parameters if supplied */ -if (!init_dh(dhparam, host)) return DEFER; +if (!init_dh(*ctxp, dhparam, host)) return DEFER; -/* Set up certificate and key */ +/* Set up certificate and key (and perhaps OCSP info) */ -rc = tls_expand_session_files(ctx, cbinfo); +rc = tls_expand_session_files(*ctxp, cbinfo); if (rc != OK) return rc; /* If we need to handle SNI, do so */ -#if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT) +#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) + { + SSL_CTX_set_tlsext_status_cb(server_ctx, tls_stapling_cb); + SSL_CTX_set_tlsext_status_arg(server_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); + SSL_CTX_set_tlsext_servername_callback(*ctxp, tls_servername_cb); + SSL_CTX_set_tlsext_servername_arg(*ctxp, cbinfo); } #endif /* Set up the RSA callback */ -SSL_CTX_set_tmp_rsa_callback(ctx, rsa_callback); +SSL_CTX_set_tmp_rsa_callback(*ctxp, rsa_callback); /* Finally, set the timeout, and we are done */ -SSL_CTX_set_timeout(ctx, ssl_session_timeout); +SSL_CTX_set_timeout(*ctxp, ssl_session_timeout); DEBUG(D_tls) debug_printf("Initialized TLS\n"); -static_cbinfo = cbinfo; +*cbp = cbinfo; return OK; } @@ -589,17 +922,17 @@ return OK; * Get name of cipher in use * *************************************************/ -/* The answer is left in a static buffer, and tls_cipher is set to point -to it. - +/* Argument: pointer to an SSL structure for the connection + buffer to use for answer + size of buffer + pointer to number of bits for cipher Returns: nothing */ static void -construct_cipher_name(SSL *ssl) +construct_cipher_name(SSL *ssl, uschar *cipherbuf, int bsize, int *bits) { -static uschar cipherbuf[256]; /* 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. */ @@ -637,11 +970,10 @@ switch (ssl->session->ssl_version) } c = (const SSL_CIPHER *) SSL_get_current_cipher(ssl); -SSL_CIPHER_get_bits(c, &tls_bits); +SSL_CIPHER_get_bits(c, bits); -string_format(cipherbuf, sizeof(cipherbuf), "%s:%s:%u", ver, - SSL_CIPHER_get_name(c), tls_bits); -tls_cipher = cipherbuf; +string_format(cipherbuf, bsize, "%s:%s:%u", ver, + SSL_CIPHER_get_name(c), *bits); DEBUG(D_tls) debug_printf("Cipher: %s\n", cipherbuf); } @@ -663,19 +995,20 @@ Arguments: host NULL in a server; the remote host in a client optional TRUE if called from a server for a host in tls_try_verify_hosts; otherwise passed as FALSE + client TRUE if called for client startup, FALSE for server startup Returns: OK/DEFER/FAIL */ static int -setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional) +setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional, BOOL client) { uschar *expcerts, *expcrl; if (!expand_check(certs, US"tls_verify_certificates", &expcerts)) return DEFER; -if (expcerts != NULL) +if (expcerts != NULL && *expcerts != '\0') { struct stat statbuf; if (!SSL_CTX_set_default_verify_paths(sctx)) @@ -767,7 +1100,7 @@ if (expcerts != NULL) SSL_CTX_set_verify(sctx, SSL_VERIFY_PEER | (optional? 0 : SSL_VERIFY_FAIL_IF_NO_PEER_CERT), - verify_callback); + client ? verify_callback_client : verify_callback_server); } return OK; @@ -785,11 +1118,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 @@ -798,16 +1126,16 @@ 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; +static uschar cipherbuf[256]; /* Check for previous activation */ -if (tls_active >= 0) +if (tls_in.active >= 0) { tls_error(US"STARTTLS received after TLS started", NULL, US""); smtp_printf("554 Already in TLS\r\n"); @@ -817,23 +1145,28 @@ 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(&server_ctx, NULL, tls_dhparam, tls_certificate, tls_privatekey, +#ifdef EXPERIMENTAL_OCSP + tls_ocsp_file, +#endif + NULL, &server_static_cbinfo); if (rc != OK) return rc; -cbinfo = static_cbinfo; +cbinfo = server_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) { uschar *s = expciphers; while (*s != 0) { if (*s == '_') *s = '-'; s++; } DEBUG(D_tls) debug_printf("required ciphers: %s\n", expciphers); - if (!SSL_CTX_set_cipher_list(ctx, CS expciphers)) + if (!SSL_CTX_set_cipher_list(server_ctx, CS expciphers)) return tls_error(US"SSL_CTX_set_cipher_list", NULL, NULL); cbinfo->server_cipher_list = expciphers; } @@ -841,25 +1174,25 @@ if (expciphers != NULL) /* If this is a host for which certificate verification is mandatory or optional, set up appropriately. */ -tls_certificate_verified = FALSE; -verify_callback_called = FALSE; +tls_in.certificate_verified = FALSE; +server_verify_callback_called = FALSE; if (verify_check_host(&tls_verify_hosts) == OK) { - rc = setup_certs(ctx, tls_verify_certificates, tls_crl, NULL, FALSE); + rc = setup_certs(server_ctx, tls_verify_certificates, tls_crl, NULL, FALSE, FALSE); if (rc != OK) return rc; - verify_optional = FALSE; + server_verify_optional = FALSE; } else if (verify_check_host(&tls_try_verify_hosts) == OK) { - rc = setup_certs(ctx, tls_verify_certificates, tls_crl, NULL, TRUE); + rc = setup_certs(server_ctx, tls_verify_certificates, tls_crl, NULL, TRUE, FALSE); if (rc != OK) return rc; - verify_optional = TRUE; + server_verify_optional = TRUE; } /* Prepare for new connection */ -if ((ssl = SSL_new(ctx)) == NULL) return tls_error(US"SSL_new", NULL, NULL); +if ((server_ssl = SSL_new(server_ctx)) == NULL) return tls_error(US"SSL_new", NULL, NULL); /* Warning: we used to SSL_clear(ssl) here, it was removed. * @@ -880,8 +1213,8 @@ make them disconnect. We need to have an explicit fflush() here, to force out the response. Other smtp_printf() calls do not need it, because in non-TLS mode, the fflush() happens when smtp_getc() is called. */ -SSL_set_session_id_context(ssl, sid_ctx, Ustrlen(sid_ctx)); -if (!tls_on_connect) +SSL_set_session_id_context(server_ssl, sid_ctx, Ustrlen(sid_ctx)); +if (!tls_in.on_connect) { smtp_printf("220 TLS go ahead\r\n"); fflush(smtp_out); @@ -890,15 +1223,15 @@ if (!tls_on_connect) /* Now negotiate the TLS session. We put our own timer on it, since it seems that the OpenSSL library doesn't. */ -SSL_set_wfd(ssl, fileno(smtp_out)); -SSL_set_rfd(ssl, fileno(smtp_in)); -SSL_set_accept_state(ssl); +SSL_set_wfd(server_ssl, fileno(smtp_out)); +SSL_set_rfd(server_ssl, fileno(smtp_in)); +SSL_set_accept_state(server_ssl); DEBUG(D_tls) debug_printf("Calling SSL_accept\n"); sigalrm_seen = FALSE; if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout); -rc = SSL_accept(ssl); +rc = SSL_accept(server_ssl); alarm(0); if (rc <= 0) @@ -915,16 +1248,22 @@ DEBUG(D_tls) debug_printf("SSL_accept was successful\n"); /* TLS has been set up. Adjust the input functions to read via TLS, and initialize things. */ -construct_cipher_name(ssl); +construct_cipher_name(server_ssl, cipherbuf, sizeof(cipherbuf), &tls_in.bits); +tls_in.cipher = cipherbuf; DEBUG(D_tls) { uschar buf[2048]; - if (SSL_get_shared_ciphers(ssl, CS buf, sizeof(buf)) != NULL) + if (SSL_get_shared_ciphers(server_ssl, CS buf, sizeof(buf)) != NULL) debug_printf("Shared ciphers: %s\n", buf); } +/* Only used by the server-side tls (tls_in), including tls_getc. + Client-side (tls_out) reads (seem to?) go via + smtp_read_response()/ip_recv(). + Hence no need to duplicate for _in and _out. + */ ssl_xfer_buffer = store_malloc(ssl_xfer_buffer_size); ssl_xfer_buffer_lwm = ssl_xfer_buffer_hwm = 0; ssl_xfer_eof = ssl_xfer_error = 0; @@ -935,7 +1274,7 @@ receive_feof = tls_feof; receive_ferror = tls_ferror; receive_smtp_buffered = tls_smtp_buffered; -tls_active = fileno(smtp_out); +tls_in.active = fileno(smtp_out); return OK; } @@ -960,11 +1299,8 @@ Argument: 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 - ------------------------------------------------------ + dh_min_bits minimum number of bits acceptable in server's DH prime + (unused in OpenSSL) timeout startup timeout Returns: OK on success @@ -976,19 +1312,23 @@ int tls_client_start(int fd, host_item *host, address_item *addr, uschar *dhparam, uschar *certificate, uschar *privatekey, uschar *sni, uschar *verify_certs, uschar *crl, - uschar *require_ciphers, uschar *require_mac, uschar *require_kx, - uschar *require_proto, int timeout) + uschar *require_ciphers, int dh_min_bits ARG_UNUSED, int timeout) { static uschar txt[256]; uschar *expciphers; X509* server_cert; int rc; +static uschar cipherbuf[256]; -rc = tls_init(host, dhparam, certificate, privatekey, addr); +rc = tls_init(&client_ctx, host, dhparam, certificate, privatekey, +#ifdef EXPERIMENTAL_OCSP + NULL, +#endif + addr, &client_static_cbinfo); if (rc != OK) return rc; -tls_certificate_verified = FALSE; -verify_callback_called = FALSE; +tls_out.certificate_verified = FALSE; +client_verify_callback_called = FALSE; if (!expand_check(require_ciphers, US"tls_require_ciphers", &expciphers)) return FAIL; @@ -1002,28 +1342,38 @@ if (expciphers != NULL) uschar *s = expciphers; while (*s != 0) { if (*s == '_') *s = '-'; s++; } DEBUG(D_tls) debug_printf("required ciphers: %s\n", expciphers); - if (!SSL_CTX_set_cipher_list(ctx, CS expciphers)) + if (!SSL_CTX_set_cipher_list(client_ctx, CS expciphers)) return tls_error(US"SSL_CTX_set_cipher_list", host, NULL); } -rc = setup_certs(ctx, verify_certs, crl, host, FALSE); +rc = setup_certs(client_ctx, verify_certs, crl, host, FALSE, TRUE); if (rc != OK) return rc; -if ((ssl = SSL_new(ctx)) == NULL) return tls_error(US"SSL_new", host, NULL); -SSL_set_session_id_context(ssl, sid_ctx, Ustrlen(sid_ctx)); -SSL_set_fd(ssl, fd); -SSL_set_connect_state(ssl); +if ((client_ssl = SSL_new(client_ctx)) == NULL) return tls_error(US"SSL_new", host, NULL); +SSL_set_session_id_context(client_ssl, sid_ctx, Ustrlen(sid_ctx)); +SSL_set_fd(client_ssl, fd); +SSL_set_connect_state(client_ssl); if (sni) { - if (!expand_check(sni, US"tls_sni", &tls_sni)) + if (!expand_check(sni, US"tls_sni", &tls_out.sni)) return FAIL; - if (!Ustrlen(tls_sni)) - tls_sni = NULL; + if (tls_out.sni == NULL) + { + DEBUG(D_tls) debug_printf("Setting TLS SNI forced to fail, not sending\n"); + } + else if (!Ustrlen(tls_out.sni)) + tls_out.sni = NULL; else { - DEBUG(D_tls) debug_printf("Setting TLS SNI \"%s\"\n", tls_sni); - SSL_set_tlsext_host_name(ssl, tls_sni); +#ifdef EXIM_HAVE_OPENSSL_TLSEXT + DEBUG(D_tls) debug_printf("Setting TLS SNI \"%s\"\n", tls_out.sni); + SSL_set_tlsext_host_name(client_ssl, tls_out.sni); +#else + DEBUG(D_tls) + debug_printf("OpenSSL at build-time lacked SNI support, ignoring \"%s\"\n", + tls_out.sni); +#endif } } @@ -1032,7 +1382,7 @@ if (sni) DEBUG(D_tls) debug_printf("Calling SSL_connect\n"); sigalrm_seen = FALSE; alarm(timeout); -rc = SSL_connect(ssl); +rc = SSL_connect(client_ssl); alarm(0); if (rc <= 0) @@ -1041,19 +1391,20 @@ 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); +server_cert = SSL_get_peer_certificate (client_ssl); if (server_cert) { - tls_peerdn = US X509_NAME_oneline(X509_get_subject_name(server_cert), + tls_out.peerdn = US X509_NAME_oneline(X509_get_subject_name(server_cert), CS txt, sizeof(txt)); - tls_peerdn = txt; + tls_out.peerdn = txt; } else - tls_peerdn = NULL; + tls_out.peerdn = NULL; -construct_cipher_name(ssl); /* Sets tls_cipher */ +construct_cipher_name(client_ssl, cipherbuf, sizeof(cipherbuf), &tls_out.bits); +tls_out.cipher = cipherbuf; -tls_active = fd; +tls_out.active = fd; return OK; } @@ -1070,6 +1421,8 @@ it refills the buffer via the SSL reading function. Arguments: none Returns: the next character or EOF + +Only used by the server-side TLS. */ int @@ -1080,12 +1433,12 @@ if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm) int error; int inbytes; - DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", ssl, + DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", server_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); - error = SSL_get_error(ssl, inbytes); + inbytes = SSL_read(server_ssl, CS ssl_xfer_buffer, ssl_xfer_buffer_size); + error = SSL_get_error(server_ssl, inbytes); alarm(0); /* SSL_ERROR_ZERO_RETURN appears to mean that the SSL session has been @@ -1102,13 +1455,13 @@ if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm) receive_ferror = smtp_ferror; receive_smtp_buffered = smtp_buffered; - SSL_free(ssl); - ssl = NULL; - tls_active = -1; - tls_bits = 0; - tls_cipher = NULL; - tls_peerdn = NULL; - tls_sni = NULL; + SSL_free(server_ssl); + server_ssl = NULL; + tls_in.active = -1; + tls_in.bits = 0; + tls_in.cipher = NULL; + tls_in.peerdn = NULL; + tls_in.sni = NULL; return smtp_getc(); } @@ -1155,11 +1508,14 @@ Arguments: Returns: the number of bytes read -1 after a failed read + +Only used by the client-side TLS. */ int -tls_read(uschar *buff, size_t len) +tls_read(BOOL is_server, uschar *buff, size_t len) { +SSL *ssl = is_server ? server_ssl : client_ssl; int inbytes; int error; @@ -1192,19 +1548,23 @@ return inbytes; /* Arguments: + is_server channel specifier buff buffer of data len number of bytes Returns: the number of bytes after a successful write, -1 after a failed write + +Used by both server-side and client-side TLS. */ int -tls_write(const uschar *buff, size_t len) +tls_write(BOOL is_server, const uschar *buff, size_t len) { int outbytes; int error; int left = len; +SSL *ssl = is_server ? server_ssl : client_ssl; DEBUG(D_tls) debug_printf("tls_do_write(%p, %d)\n", buff, left); while (left > 0) @@ -1229,6 +1589,11 @@ while (left > 0) log_write(0, LOG_MAIN, "SSL channel closed on write"); return -1; + case SSL_ERROR_SYSCALL: + log_write(0, LOG_MAIN, "SSL_write: (from %s) syscall: %s", + sender_fullhost ? sender_fullhost : US"", + strerror(errno)); + default: log_write(0, LOG_MAIN, "SSL_write error %d", error); return -1; @@ -1249,23 +1614,94 @@ would tamper with the SSL session in the parent process). Arguments: TRUE if SSL_shutdown is to be called Returns: nothing + +Used by both server-side and client-side TLS. */ void -tls_close(BOOL shutdown) +tls_close(BOOL is_server, BOOL shutdown) { -if (tls_active < 0) return; /* TLS was not active */ +SSL **sslp = is_server ? &server_ssl : &client_ssl; +int *fdp = is_server ? &tls_in.active : &tls_out.active; + +if (*fdp < 0) return; /* TLS was not active */ if (shutdown) { DEBUG(D_tls) debug_printf("tls_close(): shutting down SSL\n"); - SSL_shutdown(ssl); + SSL_shutdown(*sslp); } -SSL_free(ssl); -ssl = NULL; +SSL_free(*sslp); +*sslp = NULL; -tls_active = -1; +*fdp = -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; } @@ -1298,7 +1734,7 @@ fprintf(f, "Library version: OpenSSL: Compile: %s\n" /************************************************* -* Pseudo-random number generation * +* Random number generation * *************************************************/ /* Pseudo-random number generation. The result is not expected to be @@ -1313,16 +1749,30 @@ 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; +static pid_t pidlast = 0; +pid_t pidnow; uschar *p; uschar smallbuf[sizeof(r)]; if (max <= 1) return 0; +pidnow = getpid(); +if (pidnow != pidlast) + { + /* Although OpenSSL documents that "OpenSSL makes sure that the PRNG state + is unique for each thread", this doesn't apparently apply across processes, + so our own warning from vaguely_random_number_fallback() applies here too. + Fix per PostgreSQL. */ + if (pidlast != 0) + RAND_cleanup(); + pidlast = pidnow; + } + /* OpenSSL auto-seeds from /dev/random, etc, but this a double-check. */ if (!RAND_status()) { @@ -1349,7 +1799,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) { @@ -1526,7 +1983,7 @@ uschar keep_c; BOOL adding, item_parsed; result = 0L; -/* Prior to 4.78 we or'd in SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; removed +/* Prior to 4.80 we or'd in SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; removed * from default because it increases BEAST susceptibility. */ #ifdef SSL_OP_NO_SSLv2 result |= SSL_OP_NO_SSLv2;