X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/31f5b3492bde6a055c0c349a3d46718bd5a7e4f0..25f3b885dbfd1ba330521c8fe106876667a31bb7:/src/src/tls-openssl.c diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index eb18d64d3..89f11ce37 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 @@ -227,12 +228,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 +307,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 +362,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 +425,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 +907,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 +1253,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 +1271,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 +1285,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 +2141,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; /* 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 +2663,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 +2834,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 +2904,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 +2919,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 +3208,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 +3216,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 +3251,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. */ @@ -3390,29 +3508,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 +3645,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 +3871,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 +3972,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 @@ -4457,7 +4662,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 */ @@ -4499,11 +4703,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);