*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2019 */
-/* Copyright (c) The Exim Maintainers 2020 */
+/* Copyright (c) The Exim Maintainers 2020 - 2021 */
/* See the file NOTICE for conditions of use and distribution. */
/* Portions Copyright (c) The OpenSSL Project 1999 */
#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
# 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
# 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)
{ 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 },
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
#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];
* 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 */
+/* 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)
{
-const exim_openssl_state_st * state = arg;
-
+server_seen_alpn = TRUE;
DEBUG(D_tls)
{
debug_printf("Received TLS ALPN offer:");
if (pos + 1 + siz > inlen) siz = inlen - pos - 1;
debug_printf(" '%.*s'", siz, in + pos + 1);
}
- debug_printf("\n");
+ 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 */
- && ( 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 */
+ 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 */
+ }
}
-/* Reject unacceptable ALPN */
-/* This will be fatal to the TLS conn; would be nice to kill TCP also */
+/* 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 */
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);
+ 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
{
BIO * bp;
STACK_OF(X509) * verify_stack = *vp;
-X509 * x;
if (verify_stack)
while (sk_X509_num(verify_stack) > 0)
}
#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. */
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;
#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 *
*************************************************/
}
}
+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)
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
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)
{
void
-tls_get_cache(void)
+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
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;
{
long result, item;
uschar * exp, * end;
-uschar keep_c;
BOOL adding, item_parsed;
/* Server: send no (<= TLS1.2) session tickets */
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);