X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/97cfe3942f67200f77f6ae9b302409075e4e5792..32b11385ddced7eafe68c60eebbb2c81979ce35f:/src/src/tls-openssl.c diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index 0d653a445..590d271f7 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -80,6 +80,7 @@ change this guard and punt the issue for a while longer. */ # ifndef DISABLE_OCSP # define EXIM_HAVE_OCSP # endif +# define EXIM_HAVE_ALPN /* fail ret from hshake-cb is ignored by LibreSSL */ # else # define EXIM_NEED_OPENSSL_INIT # endif @@ -89,6 +90,10 @@ change this guard and punt the issue for a while longer. */ # endif #endif +#if LIBRESSL_VERSION_NUMBER >= 0x3040000fL +# define EXIM_HAVE_OPENSSL_CIPHER_GET_ID +#endif + #if !defined(LIBRESSL_VERSION_NUMBER) \ || LIBRESSL_VERSION_NUMBER >= 0x20010000L # if !defined(OPENSSL_NO_ECDH) @@ -227,12 +232,12 @@ static exim_openssl_option exim_openssl_options[] = { { US"no_tlsv1", SSL_OP_NO_TLSv1 }, #endif #ifdef SSL_OP_NO_TLSv1_1 -#if SSL_OP_NO_TLSv1_1 == 0x00000400L +# 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 +# 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 #endif #ifdef SSL_OP_NO_TLSv1_2 { US"no_tlsv1_2", SSL_OP_NO_TLSv1_2 }, @@ -306,6 +311,9 @@ builtin_macro_create(US"_TLS_BAD_MULTICERT_IN_OURCERT"); builtin_macro_create(US"_HAVE_TLS_OCSP"); builtin_macro_create(US"_HAVE_TLS_OCSP_LIST"); # endif +# ifdef EXIM_HAVE_ALPN +builtin_macro_create(US"_HAVE_TLS_ALPN"); +# endif } #else @@ -358,6 +366,9 @@ typedef struct { #ifdef EXIM_HAVE_OPENSSL_TLSEXT static SSL_CTX *server_sni = NULL; #endif +#ifdef EXIM_HAVE_ALPN +static BOOL server_seen_alpn = FALSE; +#endif static char ssl_errstring[256]; @@ -418,9 +429,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 @@ -903,10 +911,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) @@ -1247,10 +1257,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; } @@ -1261,8 +1275,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; @@ -1275,7 +1289,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; } @@ -2130,6 +2145,61 @@ bad: return SSL_TLSEXT_ERR_ALERT_FATAL; +#ifdef EXIM_HAVE_ALPN +/************************************************* +* Callback to handle ALPN * +*************************************************/ + +/* Called on server if tls_alpn nonblank after expansion, +when client offers ALPN, after the SNI callback. +If set and not matching the list 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) +{ +server_seen_alpn = TRUE; +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(". Our list: '%s'\n", tls_alpn); + } + +/* Look for an acceptable ALPN */ + +if ( inlen > 1 /* at least one name */ + && in[0]+1 == inlen /* filling the vector, so exactly one name */ + ) + { + const uschar * list = tls_alpn; + int sep = 0; + for (uschar * name; name = string_nextinlist(&list, &sep, NULL, 0); ) + if (Ustrncmp(in+1, name, in[0]) == 0) + { + *out = in+1; /* we checked for exactly one, so can just point to it */ + *outlen = inlen; + return SSL_TLSEXT_ERR_OK; /* use ALPN */ + } + } + +/* More than one name from clilent, or name did not match our list. */ + +/* This will be fatal to the TLS conn; would be nice to kill TCP also. +Maybe as an option in future; for now leave control to the config (must-tls). */ + +DEBUG(D_tls) debug_printf("TLS ALPN rejected\n"); +return SSL_TLSEXT_ERR_ALERT_FATAL; +} +#endif /* EXIM_HAVE_ALPN */ + + + #ifndef DISABLE_OCSP /************************************************* @@ -2597,6 +2667,21 @@ 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 + if (tls_alpn && *tls_alpn) + { + uschar * exp_alpn; + if ( expand_check(tls_alpn, US"tls_alpn", &exp_alpn, errstr) + && *exp_alpn && !isblank(*exp_alpn)) + { + tls_alpn = exp_alpn; /* subprocess so ok to overwrite */ + SSL_CTX_set_alpn_select_cb(ctx, tls_server_alpn_cb, state); + } + else + tls_alpn = NULL; + } +# endif } # ifndef DISABLE_OCSP else /* client */ @@ -2753,18 +2838,22 @@ 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; -X509 * x; +STACK_OF(X509) * verify_stack = *vp; -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 @@ -2819,6 +2908,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 @@ -2827,11 +2923,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, @@ -3117,7 +3212,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 @@ -3125,7 +3220,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; } @@ -3160,6 +3255,33 @@ if (SSL_session_reused(ssl)) } #endif +#ifdef EXIM_HAVE_ALPN +/* If require-alpn, check server_seen_alpn here. Else abort TLS */ +if (!tls_alpn || !*tls_alpn) + { DEBUG(D_tls) debug_printf("TLS: was not watching for ALPN\n"); } +else if (!server_seen_alpn) + if (verify_check_host(&hosts_require_alpn) == OK) + { + /* We'd like to send a definitive Alert but OpenSSL provides no facility */ + SSL_shutdown(ssl); + tls_error(US"handshake", NULL, US"ALPN required but not negotiated", errstr); + return FAIL; + } + else + { DEBUG(D_tls) debug_printf("TLS: no ALPN presented in handshake\n"); } +else DEBUG(D_tls) + { + const uschar * name; + unsigned len; + SSL_get0_alpn_selected(ssl, &name, &len); + if (len && name) + debug_printf("ALPN negotiated: '%.*s'\n", (int)*name, name+1); + else + debug_printf("ALPN: no protocol negotiated\n"); + } +#endif + + /* TLS has been set up. Record data for the connection, adjust the input functions to read via TLS, and initialize things. */ @@ -3228,6 +3350,7 @@ ssl_xfer_eof = ssl_xfer_error = FALSE; receive_getc = tls_getc; receive_getbuf = tls_getbuf; receive_get_cache = tls_get_cache; +receive_hasc = tls_hasc; receive_ungetc = tls_ungetc; receive_feof = tls_feof; receive_ferror = tls_ferror; @@ -3390,29 +3513,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 @@ -3521,6 +3650,47 @@ if (SSL_session_reused(exim_client_ctx->ssl)) #endif /* !DISABLE_TLS_RESUME */ +#ifdef EXIM_HAVE_ALPN +/* Expand and convert an Exim list to an ALPN list. False return for fail. +NULL plist return for silent no-ALPN. +*/ + +static BOOL +tls_alpn_plist(const uschar * tls_alpn, const uschar ** plist, unsigned * plen, + uschar ** errstr) +{ +uschar * exp_alpn; + +if (!expand_check(tls_alpn, US"tls_alpn", &exp_alpn, errstr)) + return FALSE; + +if (!exp_alpn) + { + DEBUG(D_tls) debug_printf("Setting TLS ALPN forced to fail, not sending\n"); + *plist = NULL; + } +else + { + /* The server implementation only accepts exactly one protocol name + but it's little extra code complexity in the client. */ + + const uschar * list = exp_alpn; + uschar * p = store_get(Ustrlen(exp_alpn), is_tainted(exp_alpn)), * s, * t; + int sep = 0; + uschar len; + + for (t = p; s = string_nextinlist(&list, &sep, NULL, 0); t += len) + { + *t++ = len = (uschar) Ustrlen(s); + memcpy(t, s, len); + } + *plist = (*plen = t - p) ? p : NULL; + } +return TRUE; +} +#endif /* EXIM_HAVE_ALPN */ + + /************************************************* * Start a TLS session in a client * *************************************************/ @@ -3706,6 +3876,28 @@ if (ob->tls_sni) } } +if (ob->tls_alpn) +#ifdef EXIM_HAVE_ALPN + { + const uschar * plist; + unsigned plen; + + if (!tls_alpn_plist(ob->tls_alpn, &plist, &plen, errstr)) + return FALSE; + if (plist) + if (SSL_set_alpn_protos(exim_client_ctx->ssl, plist, plen) != 0) + { + tls_error(US"alpn init", host, NULL, errstr); + return FALSE; + } + else + DEBUG(D_tls) debug_printf("Setting TLS ALPN '%s'\n", ob->tls_alpn); + } +#else + log_write(0, LOG_MAIN, "ALPN unusable with this OpenSSL library version; ignoring \"%s\"\n", + ob->tls_alpn); +#endif + #ifdef SUPPORT_DANE if (conn_args->dane) if (dane_tlsa_load(exim_client_ctx->ssl, host, &conn_args->tlsa_dnsa, errstr) != OK) @@ -3785,6 +3977,24 @@ DEBUG(D_tls) tls_client_resume_posthandshake(exim_client_ctx, tlsp); #endif +#ifdef EXIM_HAVE_ALPN +if (ob->tls_alpn) /* We requested. See what was negotiated. */ + { + const uschar * name; + unsigned len; + + SSL_get0_alpn_selected(exim_client_ctx->ssl, &name, &len); + if (len > 0) + { DEBUG(D_tls) debug_printf("ALPN negotiated %u: '%.*s'\n", len, (int)*name, name+1); } + else if (verify_check_given_host(CUSS &ob->hosts_require_alpn, host) == OK) + { + /* Would like to send a relevant fatal Alert, but OpenSSL has no API */ + tls_error(US"handshake", host, US"ALPN required but not negotiated", errstr); + return FALSE; + } + } +#endif + #ifdef SSL_get_extms_support tlsp->ext_master_secret = SSL_get_extms_support(exim_client_ctx->ssl) == 1; #endif @@ -3917,6 +4127,12 @@ if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm) return ssl_xfer_buffer[ssl_xfer_buffer_lwm++]; } +BOOL +tls_hasc(void) +{ +return ssl_xfer_buffer_lwm < ssl_xfer_buffer_hwm; +} + uschar * tls_getbuf(unsigned * len) { @@ -3941,10 +4157,13 @@ return buf; void -tls_get_cache() +tls_get_cache(unsigned lim) { #ifndef DISABLE_DKIM int n = ssl_xfer_buffer_hwm - ssl_xfer_buffer_lwm; +debug_printf("tls_get_cache\n"); +if (n > lim) + n = lim; if (n > 0) dkim_exim_verify_feed(ssl_xfer_buffer+ssl_xfer_buffer_lwm, n); #endif @@ -3955,7 +4174,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; } @@ -4053,16 +4272,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) { @@ -4120,6 +4335,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 * *************************************************/ @@ -4130,7 +4371,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 @@ -4139,24 +4381,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 */ -tls_write(ct_ctx, NULL, 0, FALSE); /* flush write buffer */ - -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 */ @@ -4180,6 +4422,7 @@ if (!o_ctx) /* server side */ receive_getc = smtp_getc; receive_getbuf = smtp_getbuf; receive_get_cache = smtp_get_cache; + receive_hasc = smtp_hasc; receive_ungetc = smtp_ungetc; receive_feof = smtp_feof; receive_ferror = smtp_ferror; @@ -4434,7 +4677,6 @@ tls_openssl_options_parse(uschar *option_spec, long *results) { long result, item; uschar * exp, * end; -uschar keep_c; BOOL adding, item_parsed; /* Server: send no (<= TLS1.2) session tickets */ @@ -4476,11 +4718,8 @@ for (uschar * s = exp; *s; /**/) return FALSE; } adding = *s++ == '+'; - for (end = s; (*end != '\0') && !isspace(*end); ++end) /**/ ; - keep_c = *end; - *end = '\0'; - item_parsed = tls_openssl_one_option_parse(s, &item); - *end = keep_c; + for (end = s; *end && !isspace(*end); ) end++; + item_parsed = tls_openssl_one_option_parse(string_copyn(s, end-s), &item); if (!item_parsed) { DEBUG(D_tls) debug_printf("openssl option setting unrecognised: \"%s\"\n", s);