From f50a063dc0b96ac95b3a7bc0aebad3b3f2534c02 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Tue, 22 Jun 2021 23:04:59 +0100 Subject: [PATCH] TLS: as server, reject connections with ALPN indicating non-smtp use --- doc/doc-txt/ChangeLog | 2 ++ src/src/tls-gnu.c | 72 +++++++++++++++++++++++++++++++++++++++++-- src/src/tls-openssl.c | 54 ++++++++++++++++++++++++++++++-- 3 files changed, 122 insertions(+), 6 deletions(-) diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog index 59211fbb7..90645e005 100644 --- a/doc/doc-txt/ChangeLog +++ b/doc/doc-txt/ChangeLog @@ -314,6 +314,8 @@ JH/54 DMARC: recent versions of the OpenDMARC library appear to have broken This affects 1.4.1-1 on Fedora 33 (1.3.2-3 is functional); and has been reported on other platforms. +JH/55 TLS: as server, reject connections with ALPN indicating non-smtp use. + Exim version 4.94 ----------------- diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c index 954fd76b1..1affba366 100644 --- a/src/src/tls-gnu.c +++ b/src/src/tls-gnu.c @@ -119,6 +119,10 @@ require current GnuTLS, then we'll drop support for the ancient libraries). # endif #endif +#if GNUTLS_VERSION_NUMBER >= 0x030200 +# define EXIM_HAVE_ALPN +#endif + #ifndef DISABLE_OCSP # include #endif @@ -278,10 +282,14 @@ static BOOL gnutls_buggy_ocsp = FALSE; static BOOL exim_testharness_disable_ocsp_validity_check = FALSE; #endif +#ifdef EXIM_HAVE_ALPN +static BOOL server_seen_alpn = FALSE; +#endif #ifdef EXIM_HAVE_TLS_RESUME static gnutls_datum_t server_sessticket_key; #endif + /* ------------------------------------------------------------------------ */ /* macros */ @@ -1062,10 +1070,18 @@ tls_server_clienthello_ext(void * ctx, unsigned tls_id, const unsigned char *data, unsigned size) { /* https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml */ -if (tls_id == 5) /* status_request */ +switch (tls_id) { - DEBUG(D_tls) debug_printf("Seen status_request extension from client\n"); - tls_in.ocsp = OCSP_NOT_RESP; + case 5: /* status_request */ + DEBUG(D_tls) debug_printf("Seen status_request extension from client\n"); + tls_in.ocsp = OCSP_NOT_RESP; + break; +#ifdef EXIM_HAVE_ALPN + case 16: /* Application Layer Protocol Notification */ + DEBUG(D_tls) debug_printf("Seen ALPN extension from client\n"); + server_seen_alpn = TRUE; + break; +#endif } return 0; } @@ -2791,6 +2807,26 @@ if (gnutls_session_is_resumed(state->session)) } } #endif + + +#ifdef EXIM_HAVE_ALPN +static void +tls_server_set_acceptable_alpns(exim_gnutls_state_st * state) +{ +int rc; +gnutls_datum_t protocols[2] = {[0] = {.data = US"smtp", .size = 4}, + [1] = {.data = US"esmtp", .size = 5}}; + +/* Set non-mandatory set of protocol names */ +if (!(rc = gnutls_alpn_set_protocols(state->session, protocols, 2, 0))) + gnutls_handshake_set_hook_function(state->session, + GNUTLS_HANDSHAKE_ANY, GNUTLS_HOOK_POST, tls_server_hook_cb); +else + DEBUG(D_tls) + debug_printf("setting alpn protocols: %s\n", US gnutls_strerror(rc)); +} +#endif + /* ------------------------------------------------------------------------ */ /* Exported functions */ @@ -2847,6 +2883,10 @@ DEBUG(D_tls) debug_printf("initialising GnuTLS as a server\n"); #endif } +#ifdef EXIM_HAVE_ALPN +tls_server_set_acceptable_alpns(state); +#endif + #ifdef EXIM_HAVE_TLS_RESUME tls_server_resume_prehandshake(state); #endif @@ -2962,6 +3002,32 @@ tls_server_resume_posthandshake(state); DEBUG(D_tls) post_handshake_debug(state); +#ifdef EXIM_HAVE_ALPN +if (server_seen_alpn) + { + /* The client offered ALPN. We were set up with a nonmandatory list; + see what was negotiated. We require a match now, given that something + was offered. */ + gnutls_datum_t p = {.size = 0}; + int rc = gnutls_alpn_get_selected_protocol(state->session, &p); + if (!rc || rc == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) + { + if (p.size == 0) + { + *errstr = US"ALPN rejected"; + return FAIL; + } + else + DEBUG(D_tls) + debug_printf("ALPN negotiated: %.*s\n", (int)p.size, p.data); + } + else + DEBUG(D_tls) + debug_printf("getting alpn protocol: %s\n", US gnutls_strerror(rc)); + + } +#endif + /* Verify after the fact */ if (!verify_certificate(state, errstr)) diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index cc72b2eeb..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 @@ -2137,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 /************************************************* @@ -2604,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 */ -- 2.30.2