X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/dc4ab0a186edc8b270c8fa486104fabc567d25e7..dbac5a049acbe645a816b4a5e895c5be0de53483:/src/src/tls-openssl.c diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index 050b36c40..9692033f0 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -48,6 +48,7 @@ functions from the OpenSSL library. */ #if OPENSSL_VERSION_NUMBER >= 0x10100000L # define EXIM_HAVE_OCSP_RESP_COUNT # define OPENSSL_AUTO_SHA256 +# define EXIM_HAVE_ALPN #else # define EXIM_HAVE_EPHEM_RSA_KEX # define EXIM_HAVE_RAND_PSEUDO @@ -418,9 +419,6 @@ setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, uschar ** errstr ); /* Callbacks */ -#ifdef EXIM_HAVE_OPENSSL_TLSEXT -static int tls_servername_cb(SSL *s, int *ad ARG_UNUSED, void *arg); -#endif #ifndef DISABLE_OCSP static int tls_server_stapling_cb(SSL *s, void *arg); #endif @@ -704,12 +702,12 @@ if (Ustrcmp(exp_curve, "auto") == 0) #else # if defined SSL_CTRL_SET_ECDH_AUTO DEBUG(D_tls) debug_printf( - "ECDH OpenSSL 1.0.2+ temp key parameter settings: autoselection\n"); + "ECDH OpenSSL 1.0.2+: temp key parameter settings: autoselection\n"); SSL_CTX_set_ecdh_auto(sctx, 1); return TRUE; # else DEBUG(D_tls) debug_printf( - "ECDH OpenSSL 1.1.0+ temp key parameter settings: default selection\n"); + "ECDH OpenSSL 1.1.0+: temp key parameter settings: default selection\n"); return TRUE; # endif #endif @@ -794,6 +792,9 @@ return rsa_key; /* Create and install a selfsigned certificate, for use in server mode */ +/*XXX we could arrange to call this during prelo for a null tls_certificate option. +The normal cache inval + relo will suffice. +Just need a timer for inval. */ static int tls_install_selfsign(SSL_CTX * sctx, uschar ** errstr) @@ -804,6 +805,7 @@ RSA * rsa; X509_NAME * name; uschar * where; +DEBUG(D_tls) debug_printf("TLS: generating selfsigned server cert\n"); where = US"allocating pkey"; if (!(pkey = EVP_PKEY_new())) goto err; @@ -823,7 +825,7 @@ if (!EVP_PKEY_assign_RSA(pkey, rsa)) X509_set_version(x509, 2); /* N+1 - version 3 */ ASN1_INTEGER_set(X509_get_serialNumber(x509), 1); X509_gmtime_adj(X509_get_notBefore(x509), 0); -X509_gmtime_adj(X509_get_notAfter(x509), (long)60 * 60); /* 1 hour */ +X509_gmtime_adj(X509_get_notAfter(x509), (long)2 * 60 * 60); /* 2 hour */ X509_set_pubkey(x509, pkey); name = X509_get_subject_name(x509); @@ -899,10 +901,12 @@ DEBUG(D_tls) str = where & SSL_CB_READ ? US"read" : US"write", SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret)); else if (where & SSL_CB_EXIT) - if (ret == 0) - debug_printf("%s: failed in %s\n", str, SSL_state_string_long(s)); - else if (ret < 0) - debug_printf("%s: error in %s\n", str, SSL_state_string_long(s)); + { + if (ret == 0) + debug_printf("%s: failed in %s\n", str, SSL_state_string_long(s)); + else if (ret < 0) + debug_printf("%s: error in %s\n", str, SSL_state_string_long(s)); + } else if (where & SSL_CB_HANDSHAKE_START) debug_printf("%s: hshake start: %s\n", str, SSL_state_string_long(s)); else if (where & SSL_CB_HANDSHAKE_DONE) @@ -1243,10 +1247,14 @@ int status, reason, i; DEBUG(D_tls) debug_printf("tls_ocsp_file (%s) '%s'\n", is_pem ? "PEM" : "DER", filename); +if (!filename || !*filename) return; + +ERR_clear_error(); if (!(bio = BIO_new_file(CS filename, "rb"))) { - DEBUG(D_tls) debug_printf("Failed to open OCSP response file \"%s\"\n", - filename); + log_write(0, LOG_MAIN|LOG_PANIC, + "Failed to open OCSP response file \"%s\": %.100s", + filename, ERR_reason_error_string(ERR_get_error())); return; } @@ -1257,8 +1265,8 @@ if (is_pem) long len; if (!PEM_read_bio(bio, &dummy, &dummy, &data, &len)) { - DEBUG(D_tls) debug_printf("Failed to read PEM file \"%s\"\n", - filename); + log_write(0, LOG_MAIN|LOG_PANIC, "Failed to read PEM file \"%s\": %.100s", + filename, ERR_reason_error_string(ERR_get_error())); return; } freep = data; @@ -1271,7 +1279,8 @@ BIO_free(bio); if (!resp) { - DEBUG(D_tls) debug_printf("Error reading OCSP response.\n"); + log_write(0, LOG_MAIN|LOG_PANIC, "Error reading OCSP response from \"%s\": %s", + filename, ERR_reason_error_string(ERR_get_error())); return; } @@ -1619,18 +1628,19 @@ return OK; } -static void +static unsigned tls_server_creds_init(void) { SSL_CTX * ctx; uschar * dummy_errstr; +unsigned lifetime = 0; tls_openssl_init(); state_server.lib_state = null_tls_preload; if (lib_ctx_new(&ctx, NULL, &dummy_errstr) != OK) - return; + return 0; state_server.lib_state.lib_ctx = ctx; /* Preload DH params and EC curve */ @@ -1677,6 +1687,18 @@ if ( opt_set_and_noexpand(tls_certificate) state_server.lib_state.conn_certs = TRUE; } } +else if ( !tls_certificate && !tls_privatekey +# ifndef DISABLE_OCSP + && !tls_ocsp_file +#endif + ) + { /* Generate & preload a selfsigned cert. No files to watch. */ + if (tls_expand_session_files(ctx, &state_server, &dummy_errstr) == OK) + { + state_server.lib_state.conn_certs = TRUE; + lifetime = f.running_in_test_harness ? 2 : 60 * 60; /* 1 hour */ + } + } else DEBUG(D_tls) debug_printf("TLS: not preloading server certs\n"); @@ -1717,6 +1739,7 @@ if (opt_set_and_noexpand(tls_require_ciphers)) } else DEBUG(D_tls) debug_printf("TLS: not preloading cipher list for server\n"); +return lifetime; } @@ -2112,6 +2135,53 @@ bad: return SSL_TLSEXT_ERR_ALERT_FATAL; +#ifdef EXIM_HAVE_ALPN +/************************************************* +* Callback to handle ALPN * +*************************************************/ + +/* SSL_CTX_set_alpn_select_cb() */ +/* Called on server when client offers ALPN, after the SNI callback. +If set and not e?smtp then we dump the connection */ + +static int +tls_server_alpn_cb(SSL *ssl, const uschar ** out, uschar * outlen, + const uschar * in, unsigned int inlen, void * arg) +{ +const exim_openssl_state_st * state = arg; + +DEBUG(D_tls) + { + debug_printf("Received TLS ALPN offer:"); + for (int pos = 0, siz; pos < inlen; pos += siz+1) + { + siz = in[pos]; + if (pos + 1 + siz > inlen) siz = inlen - pos - 1; + debug_printf(" '%.*s'", siz, in + pos + 1); + } + debug_printf("\n"); + } + +/* Look for an acceptable ALPN */ +if ( inlen > 1 /* at least one name */ + && in[0]+1 == inlen /* filling the vector, so exactly one name */ + && ( Ustrncmp(in+1, "smtp", in[0]) == 0 + || Ustrncmp(in+1, "esmtp", in[0]) == 0 + ) ) + { + *out = in; /* we checked for exactly one, so can just point to it */ + *outlen = inlen; + return SSL_TLSEXT_ERR_OK; /* use ALPN */ + } + +/* Reject unacceptable ALPN */ +/* This will be fatal to the TLS conn; would be nice to kill TCP also */ +return SSL_TLSEXT_ERR_ALERT_FATAL; +} +#endif /* EXIM_HAVE_ALPN */ + + + #ifndef DISABLE_OCSP /************************************************* @@ -2579,6 +2649,9 @@ if (!host) /* server */ tls_certificate */ SSL_CTX_set_tlsext_servername_callback(ctx, tls_servername_cb); SSL_CTX_set_tlsext_servername_arg(ctx, state); +# ifdef EXIM_HAVE_ALPN + SSL_CTX_set_alpn_select_cb(ctx, tls_server_alpn_cb, state); +# endif } # ifndef DISABLE_OCSP else /* client */ @@ -2735,18 +2808,23 @@ if (tlsp->peercert) /* Load certs from file, return TRUE on success */ static BOOL -chain_from_pem_file(const uschar * file, STACK_OF(X509) * verify_stack) +chain_from_pem_file(const uschar * file, STACK_OF(X509) ** vp) { BIO * bp; +STACK_OF(X509) * verify_stack = *vp; X509 * x; -while (sk_X509_num(verify_stack) > 0) - X509_free(sk_X509_pop(verify_stack)); +if (verify_stack) + while (sk_X509_num(verify_stack) > 0) + X509_free(sk_X509_pop(verify_stack)); +else + verify_stack = sk_X509_new_null(); if (!(bp = BIO_new_file(CS file, "r"))) return FALSE; -while ((x = PEM_read_bio_X509(bp, NULL, 0, NULL))) +for (X509 * x; x = PEM_read_bio_X509(bp, NULL, 0, NULL); ) sk_X509_push(verify_stack, x); BIO_free(bp); +*vp = verify_stack; return TRUE; } #endif @@ -2801,6 +2879,13 @@ if (expcerts && *expcerts) { file = NULL; dir = expcerts; } else { + STACK_OF(X509) * verify_stack = +#ifndef DISABLE_OCSP + !host ? state_server.verify_stack : +#endif + NULL; + STACK_OF(X509) ** vp = &verify_stack; + file = expcerts; dir = NULL; #ifndef DISABLE_OCSP /* In the server if we will be offering an OCSP proof, load chain from @@ -2809,11 +2894,10 @@ if (expcerts && *expcerts) /*XXX Glitch! The file here is tls_verify_certs: the chain for verifying the client cert. This is inconsistent with the need to verify the OCSP proof of the server cert. */ - if ( !host && statbuf.st_size > 0 && state_server.u_ocsp.server.file - && !chain_from_pem_file(file, state_server.verify_stack) + && !chain_from_pem_file(file, vp) ) { log_write(0, LOG_MAIN|LOG_PANIC, @@ -3099,7 +3183,7 @@ if (rc <= 0) /* Handle genuine errors */ case SSL_ERROR_SSL: { - uschar * s = US"SSL_accept"; + uschar * s = NULL; int r = ERR_GET_REASON(ERR_peek_error()); if ( r == SSL_R_WRONG_VERSION_NUMBER #ifdef SSL_R_VERSION_TOO_LOW @@ -3107,7 +3191,7 @@ if (rc <= 0) #endif || r == SSL_R_UNKNOWN_PROTOCOL || r == SSL_R_UNSUPPORTED_PROTOCOL) s = string_sprintf("%s (%s)", s, SSL_get_version(ssl)); - (void) tls_error(s, NULL, sigalrm_seen ? US"timed out" : NULL, errstr); + (void) tls_error(US"SSL_accept", NULL, sigalrm_seen ? US"timed out" : s, errstr); return FAIL; } @@ -3372,29 +3456,35 @@ if (tlsp->host_resumable) debug_printf("decoding session: %s\n", ssl_errstring); } } -#ifdef EXIM_HAVE_SESSION_TICKET - else if ( SSL_SESSION_get_ticket_lifetime_hint(ss) + dt->time_stamp - < time(NULL)) + else { - DEBUG(D_tls) debug_printf("session expired\n"); - dbfn_delete(dbm_file, key); - } + unsigned long lifetime = +#ifdef EXIM_HAVE_SESSION_TICKET + SSL_SESSION_get_ticket_lifetime_hint(ss); +#else /* Use, fairly arbitrilarily, what we as server would */ + f.running_in_test_harness ? 6 : ssl_session_timeout; #endif - else if (!SSL_set_session(ssl, ss)) - { - DEBUG(D_tls) + if (lifetime + dt->time_stamp < time(NULL)) { - ERR_error_string_n(ERR_get_error(), - ssl_errstring, sizeof(ssl_errstring)); - debug_printf("applying session to ssl: %s\n", ssl_errstring); + DEBUG(D_tls) debug_printf("session expired\n"); + dbfn_delete(dbm_file, key); + } + else if (!SSL_set_session(ssl, ss)) + { + DEBUG(D_tls) + { + ERR_error_string_n(ERR_get_error(), + ssl_errstring, sizeof(ssl_errstring)); + debug_printf("applying session to ssl: %s\n", ssl_errstring); + } + } + else + { + DEBUG(D_tls) debug_printf("good session\n"); + tlsp->resumption |= RESUME_CLIENT_SUGGESTED; + tlsp->verify_override = dt->verify_override; + tlsp->ocsp = dt->ocsp; } - } - else - { - DEBUG(D_tls) debug_printf("good session\n"); - tlsp->resumption |= RESUME_CLIENT_SUGGESTED; - tlsp->verify_override = dt->verify_override; - tlsp->ocsp = dt->ocsp; } } else @@ -3923,7 +4013,7 @@ return buf; void -tls_get_cache() +tls_get_cache(void) { #ifndef DISABLE_DKIM int n = ssl_xfer_buffer_hwm - ssl_xfer_buffer_lwm; @@ -3937,7 +4027,7 @@ BOOL tls_could_read(void) { return ssl_xfer_buffer_lwm < ssl_xfer_buffer_hwm - || SSL_pending(state_server.lib_state.lib_ssl) > 0; + || SSL_pending(state_server.lib_state.lib_ssl) > 0; } @@ -4035,16 +4125,12 @@ if (more || corked) { if (!len) buff = US &error; /* dummy just so that string_catn is ok */ -#ifndef DISABLE_PIPE_CONNECT int save_pool = store_pool; store_pool = POOL_PERM; -#endif corked = string_catn(corked, buff, len); -#ifndef DISABLE_PIPE_CONNECT store_pool = save_pool; -#endif if (more) { @@ -4102,6 +4188,32 @@ return olen; +/* +Arguments: + ct_ctx client TLS context pointer, or NULL for the one global server context +*/ + +void +tls_shutdown_wr(void * ct_ctx) +{ +exim_openssl_client_tls_ctx * o_ctx = ct_ctx; +SSL ** sslp = o_ctx ? &o_ctx->ssl : (SSL **) &state_server.lib_state.lib_ssl; +int * fdp = o_ctx ? &tls_out.active.sock : &tls_in.active.sock; +int rc; + +if (*fdp < 0) return; /* TLS was not active */ + +tls_write(ct_ctx, NULL, 0, FALSE); /* flush write buffer */ + +HDEBUG(D_transport|D_tls|D_acl|D_v) debug_printf_indent(" SMTP(TLS shutdown)>>\n"); +rc = SSL_shutdown(*sslp); +if (rc < 0) DEBUG(D_tls) + { + ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring)); + debug_printf("SSL_shutdown: %s\n", ssl_errstring); + } +} + /************************************************* * Close down a TLS session * *************************************************/ @@ -4112,7 +4224,8 @@ would tamper with the SSL session in the parent process). Arguments: ct_ctx client TLS context pointer, or NULL for the one global server context - shutdown 1 if TLS close-alert is to be sent, + do_shutdown 0 no data-flush or TLS close-alert + 1 if TLS close-alert is to be sent, 2 if also response to be waited for Returns: nothing @@ -4121,22 +4234,24 @@ Used by both server-side and client-side TLS. */ void -tls_close(void * ct_ctx, int shutdown) +tls_close(void * ct_ctx, int do_shutdown) { exim_openssl_client_tls_ctx * o_ctx = ct_ctx; -SSL **sslp = o_ctx ? &o_ctx->ssl : (SSL **) &state_server.lib_state.lib_ssl; -int *fdp = o_ctx ? &tls_out.active.sock : &tls_in.active.sock; +SSL ** sslp = o_ctx ? &o_ctx->ssl : (SSL **) &state_server.lib_state.lib_ssl; +int * fdp = o_ctx ? &tls_out.active.sock : &tls_in.active.sock; if (*fdp < 0) return; /* TLS was not active */ -if (shutdown) +if (do_shutdown) { int rc; DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS%s\n", - shutdown > 1 ? " (with response-wait)" : ""); + do_shutdown > 1 ? " (with response-wait)" : ""); + + tls_write(ct_ctx, NULL, 0, FALSE); /* flush write buffer */ if ( (rc = SSL_shutdown(*sslp)) == 0 /* send "close notify" alert */ - && shutdown > 1) + && do_shutdown > 1) { ALARM(2); rc = SSL_shutdown(*sslp); /* wait for response */