summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/OS/Makefile-Base16
-rwxr-xr-xsrc/scripts/MakeLinks2
-rw-r--r--src/src/EDITME5
-rw-r--r--src/src/dane-gnu.c21
-rw-r--r--src/src/dane.c4
-rw-r--r--src/src/tls-gnu.c174
-rw-r--r--src/src/transports/smtp.c11
7 files changed, 164 insertions, 69 deletions
diff --git a/src/OS/Makefile-Base b/src/OS/Makefile-Base
index dcd87c29c..bb250ff91 100644
--- a/src/OS/Makefile-Base
+++ b/src/OS/Makefile-Base
@@ -471,13 +471,13 @@ convert4r4: config ../src/convert4r4.src
OBJ_WITH_CONTENT_SCAN = malware.o mime.o regex.o spam.o spool_mbox.o
OBJ_EXPERIMENTAL = bmi_spam.o \
- dane.o \
- dcc.o \
- dmarc.o \
- imap_utf7.o \
- spf.o \
- srs.o \
- utf8.o
+ dane.o \
+ dcc.o \
+ dmarc.o \
+ imap_utf7.o \
+ spf.o \
+ srs.o \
+ utf8.o
# Targets for final binaries; the main one has a build number which is
# updated each time. We don't bother with that for the auxiliaries.
@@ -821,7 +821,7 @@ spool_mbox.o: $(HDRS) spool_mbox.c
# Dependencies for EXPERIMENTAL_* modules
bmi_spam.o: $(HDRS) bmi_spam.c
-dane.o: $(HDRS) dane.c dane-gnu.c dane-openssl.c
+dane.o: $(HDRS) dane.c dane-openssl.c
dcc.o: $(HDRS) dcc.h dcc.c
dmarc.o: $(HDRS) pdkim/pdkim.h dmarc.h dmarc.c
imap_utf7.o: $(HDRS) imap_utf7.c
diff --git a/src/scripts/MakeLinks b/src/scripts/MakeLinks
index 770591dc8..7fc1d19b4 100755
--- a/src/scripts/MakeLinks
+++ b/src/scripts/MakeLinks
@@ -122,7 +122,7 @@ do
done
# EXPERIMENTAL_*
-for f in bmi_spam.c bmi_spam.h dcc.c dcc.h dane.c dane-gnu.c dane-openssl.c \
+for f in bmi_spam.c bmi_spam.h dcc.c dcc.h dane.c dane-openssl.c \
danessl.h imap_utf7.c spf.c spf.h srs.c srs.h utf8.c
do
ln -s ../src/$f $f
diff --git a/src/src/EDITME b/src/src/EDITME
index 72e26ce0e..947a4db0e 100644
--- a/src/src/EDITME
+++ b/src/src/EDITME
@@ -489,7 +489,7 @@ EXIM_MONITOR=eximon.bin
# Uncomment the following line to add DANE support
# Note: Enabling this unconditionally overrides DISABLE_DNSSEC
-# Note: DANE is only supported when using OpenSSL
+# For DANE under GnuTLS we need an additional library. See TLS_LIBS below.
# EXPERIMENTAL_DANE=yes
# Uncomment the following to include extra information in fail DSN message (bounces)
@@ -797,6 +797,9 @@ HEADERS_CHARSET="ISO-8859-1"
# or
# TLS_LIBS=-L/opt/gnu/lib -lgnutls -ltasn1 -lgcrypt
+# For DANE under GnuTLS we need an additional library.
+TLS_LIBS += -lgnutls-dane
+
# TLS_LIBS is included only on the command for linking Exim itself, not on any
# auxiliary programs. If the include files are not in a standard place, you can
# set TLS_INCLUDE to specify where they are, for example:
diff --git a/src/src/dane-gnu.c b/src/src/dane-gnu.c
deleted file mode 100644
index b98bffad6..000000000
--- a/src/src/dane-gnu.c
+++ /dev/null
@@ -1,21 +0,0 @@
-/*************************************************
-* Exim - an Internet mail transport agent *
-*************************************************/
-
-/* Copyright (c) University of Cambridge 1995 - 2013 */
-/* See the file NOTICE for conditions of use and distribution. */
-
-/* This file (will) provide DANE support for Exim using the GnuTLS library,
-but is not yet an available supported implementation. This file is #included
-into dane.c when USE_GNUTLS has been set. */
-
-/* As of March 2014, the reference implementation for DANE that we are
-using was written by Viktor Dukhovny and it supports OpenSSL only. At
-some point we will add GnuTLS support, but for right now just abort the
-build and explain why. */
-
-
-#error No support for DANE using GnuTLS yet.
-
-
-/* End of dane-gnu.c */
diff --git a/src/src/dane.c b/src/src/dane.c
index 137c75418..b632d80dd 100644
--- a/src/src/dane.c
+++ b/src/src/dane.c
@@ -38,9 +38,7 @@ static void dummy(int x) { dummy(x-1); }
# error DANE support requires that the DNS resolver library supports DNSSEC
# endif
-# ifdef USE_GNUTLS
-# include "dane-gnu.c"
-# else
+# ifndef USE_GNUTLS
# include "dane-openssl.c"
# endif
diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c
index e69fc8bee..9f166691a 100644
--- a/src/src/tls-gnu.c
+++ b/src/src/tls-gnu.c
@@ -66,10 +66,16 @@ require current GnuTLS, then we'll drop support for the ancient libraries).
#if GNUTLS_VERSION_NUMBER >= 0x030506 && !defined(DISABLE_OCSP)
# define SUPPORT_SRV_OCSP_STACK
#endif
+#if GNUTLS_VERSION_NUMBER >= 0x030000 && defined(EXPERIMENTAL_DANE)
+# define SUPPORT_DANE
+#endif
#ifndef DISABLE_OCSP
# include <gnutls/ocsp.h>
#endif
+#ifdef SUPPORT_DANE
+# include <gnutls/dane.h>
+#endif
/* GnuTLS 2 vs 3
@@ -85,7 +91,7 @@ Changes:
/* Values for verify_requirement */
enum peer_verify_requirement
- { VERIFY_NONE, VERIFY_OPTIONAL, VERIFY_REQUIRED };
+ { VERIFY_NONE, VERIFY_OPTIONAL, VERIFY_REQUIRED, VERIFY_DANE };
/* This holds most state for server or client; with this, we can set up an
outbound TLS-enabled connection in an ACL callout, while not stomping all
@@ -106,6 +112,7 @@ typedef struct exim_gnutls_state {
int fd_in;
int fd_out;
BOOL peer_cert_verified;
+ BOOL peer_dane_verified;
BOOL trigger_sni_changes;
BOOL have_set_peerdn;
const struct host_item *host;
@@ -130,6 +137,10 @@ typedef struct exim_gnutls_state {
#ifndef DISABLE_EVENT
uschar *event_action;
#endif
+#ifdef SUPPORT_DANE
+ char * const * dane_data;
+ const int * dane_data_len;
+#endif
tls_support *tlsp; /* set in tls_init() */
@@ -148,6 +159,7 @@ static const exim_gnutls_state_st exim_gnutls_state_init = {
.fd_in = -1,
.fd_out = -1,
.peer_cert_verified = FALSE,
+ .peer_dane_verified = FALSE,
.trigger_sni_changes =FALSE,
.have_set_peerdn = FALSE,
.host = NULL,
@@ -438,6 +450,9 @@ tlsp->cipher = state->ciphersuite;
DEBUG(D_tls) debug_printf("cipher: %s\n", state->ciphersuite);
tlsp->certificate_verified = state->peer_cert_verified;
+#ifdef SUPPORT_DANE
+tlsp->dane_verified = state->peer_dane_verified;
+#endif
/* note that tls_channelbinding_b64 is not saved to the spool file, since it's
only available for use for authenticators while this TLS session is running. */
@@ -1549,8 +1564,8 @@ gnutls_certificate_set_verify_function() to fail the handshake if we dislike
the peer information, but that's too new for some OSes.
Arguments:
- state exim_gnutls_state_st *
- errstr where to put an error message
+ state exim_gnutls_state_st *
+ errstr where to put an error message
Returns:
FALSE if the session should be rejected
@@ -1561,7 +1576,10 @@ static BOOL
verify_certificate(exim_gnutls_state_st *state, uschar ** errstr)
{
int rc;
-unsigned int verify;
+uint verify;
+
+if (state->verify_requirement == VERIFY_NONE)
+ return TRUE;
*errstr = NULL;
@@ -1571,10 +1589,49 @@ if ((rc = peer_status(state, errstr)) != OK)
*errstr = US"certificate not supplied";
}
else
+
+ {
+#ifdef SUPPORT_DANE
+ if (state->verify_requirement == VERIFY_DANE && state->host)
+ {
+ /* Using dane_verify_session_crt() would be easy, as it does it all for us
+ including talking to a DNS resolver. But we want to do that bit ourselves
+ as the testsuite intercepts and fakes its own DNS environment. */
+
+ dane_state_t s;
+ dane_query_t r;
+ const gnutls_datum_t * certlist;
+ uint lsize;
+
+ certlist = gnutls_certificate_get_peers(state->session, &lsize);
+
+ if ( (rc = dane_state_init(&s, 0))
+ || (rc = dane_raw_tlsa(s, &r, state->dane_data, state->dane_data_len,
+ 1, 0))
+ || (rc = dane_verify_crt_raw(s, certlist, lsize,
+ gnutls_certificate_type_get(state->session),
+ r, 0, 0, &verify))
+ )
+
+ {
+ *errstr = string_sprintf("TLSA record problem: %s", dane_strerror(rc));
+ goto badcert;
+ }
+ if (verify != 0)
+ {
+ gnutls_datum_t str;
+ (void) dane_verification_status_print(verify, &str, 0);
+ *errstr = US str.data; /* don't bother to free */
+ goto badcert;
+ }
+ state->peer_dane_verified = TRUE;
+ }
+#endif
+
rc = gnutls_certificate_verify_peers2(state->session, &verify);
+ }
-/* Handle the result of verification. INVALID seems to be set as well
-as REVOKED, but leave the test for both. */
+/* Handle the result of verification. INVALID is set if any others are. */
if (rc < 0 ||
verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED)
@@ -1590,11 +1647,7 @@ if (rc < 0 ||
*errstr, state->peerdn ? state->peerdn : US"<unset>");
if (state->verify_requirement >= VERIFY_REQUIRED)
- {
- gnutls_alert_send(state->session,
- GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE);
- return FALSE;
- }
+ goto badcert;
DEBUG(D_tls)
debug_printf("TLS verify failure overridden (host in tls_try_verify_hosts)\n");
}
@@ -1614,11 +1667,7 @@ else
DEBUG(D_tls)
debug_printf("TLS certificate verification failed: cert name mismatch\n");
if (state->verify_requirement >= VERIFY_REQUIRED)
- {
- gnutls_alert_send(state->session,
- GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE);
- return FALSE;
- }
+ goto badcert;
return TRUE;
}
}
@@ -1628,8 +1677,11 @@ else
}
state->tlsp->peerdn = state->peerdn;
-
return TRUE;
+
+badcert:
+ gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE);
+ return FALSE;
}
@@ -1955,8 +2007,7 @@ DEBUG(D_tls) debug_printf("gnutls_handshake was successful\n");
/* Verify after the fact */
-if ( state->verify_requirement != VERIFY_NONE
- && !verify_certificate(state, errstr))
+if (!verify_certificate(state, errstr))
{
if (state->verify_requirement != VERIFY_OPTIONAL)
{
@@ -2014,6 +2065,55 @@ if (verify_check_given_host(&ob->tls_verify_cert_hostnames, host) == OK)
}
+
+
+#ifdef SUPPORT_DANE
+/* Given our list of RRs from the TLSA lookup, build a lookup block in
+GnuTLS-DANE's preferred format. Hang it on the state str for later
+use in DANE verification.
+
+We point at the dnsa data not copy it, so it must remain valid until
+after verification is done.*/
+
+static void
+dane_tlsa_load(exim_gnutls_state_st * state, dns_answer * dnsa)
+{
+dns_record * rr;
+dns_scan dnss;
+int i;
+const char ** dane_data;
+int * dane_data_len;
+
+for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS), i = 1;
+ rr;
+ rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)
+ ) if (rr->type == T_TLSA) i++;
+
+dane_data = store_get(i * sizeof(uschar *));
+dane_data_len = store_get(i * sizeof(int));
+
+for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS), i = 0;
+ rr;
+ rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)
+ ) if (rr->type == T_TLSA)
+ {
+ const uschar * p = rr->data;
+ uint8_t usage = *p;
+
+ tls_out.tlsa_usage |= 1<<usage;
+ dane_data[i] = p;
+ dane_data_len[i++] = rr->size;
+ }
+dane_data[i] = NULL;
+dane_data_len[i] = 0;
+
+state->dane_data = (char * const *)dane_data;
+state->dane_data_len = dane_data_len;
+}
+#endif
+
+
+
/*************************************************
* Start a TLS session in a client *
*************************************************/
@@ -2025,7 +2125,11 @@ Arguments:
host connected host (for messages)
addr the first address (not used)
tb transport (always smtp)
-
+ tlsa_dnsa non-NULL, either request or require dane for this host, and
+ a TLSA record found. Therefore, dane verify required.
+ Which implies cert must be requested and supplied, dane
+ verify must pass, and cert verify irrelevant (incl.
+ hostnames), and (caller handled) require_tls
errstr error string pointer
Returns: OK/DEFER/FAIL (because using common functions),
@@ -2037,14 +2141,14 @@ tls_client_start(int fd, host_item *host,
address_item *addr ARG_UNUSED,
transport_instance * tb,
#ifdef EXPERIMENTAL_DANE
- dns_answer * tlsa_dnsa ARG_UNUSED,
+ dns_answer * tlsa_dnsa,
#endif
uschar ** errstr)
{
smtp_transport_options_block *ob =
(smtp_transport_options_block *)tb->options_block;
int rc;
-exim_gnutls_state_st *state = NULL;
+exim_gnutls_state_st * state = NULL;
#ifndef DISABLE_OCSP
BOOL require_ocsp =
verify_check_given_host(&ob->hosts_require_ocsp, host) == OK;
@@ -2080,12 +2184,23 @@ if ((rc = tls_init(host, ob->tls_certificate, ob->tls_privatekey,
set but both tls_verify_hosts and tls_try_verify_hosts are unset. Check only
the specified host patterns if one of them is defined */
-if ( ( state->exp_tls_verify_certificates
- && !ob->tls_verify_hosts
- && (!ob->tls_try_verify_hosts || !*ob->tls_try_verify_hosts)
- )
- || verify_check_given_host(&ob->tls_verify_hosts, host) == OK
- )
+#ifdef SUPPORT_DANE
+if (tlsa_dnsa)
+ {
+ DEBUG(D_tls)
+ debug_printf("TLS: server certificate DANE required.\n");
+ state->verify_requirement = VERIFY_DANE;
+ gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE);
+ dane_tlsa_load(state, tlsa_dnsa);
+ }
+else
+#endif
+ if ( ( state->exp_tls_verify_certificates
+ && !ob->tls_verify_hosts
+ && (!ob->tls_try_verify_hosts || !*ob->tls_try_verify_hosts)
+ )
+ || verify_check_given_host(&ob->tls_verify_hosts, host) == OK
+ )
{
tls_client_setup_hostname_checks(host, state, ob);
DEBUG(D_tls)
@@ -2160,8 +2275,7 @@ DEBUG(D_tls) debug_printf("gnutls_handshake was successful\n");
/* Verify late */
-if (state->verify_requirement != VERIFY_NONE &&
- !verify_certificate(state, errstr))
+if (!verify_certificate(state, errstr))
return tls_error(US"certificate verification failed", *errstr, state->host, errstr);
#ifndef DISABLE_OCSP
diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c
index 61e8d8a4f..a1b677e19 100644
--- a/src/src/transports/smtp.c
+++ b/src/src/transports/smtp.c
@@ -1217,14 +1217,15 @@ DEBUG(D_transport)
switch (rc)
{
- case DNS_SUCCEED:
- if (sec) return OK;
-
- log_write(0, LOG_MAIN, "DANE error: TLSA lookup not DNSSEC");
- /*FALLTHROUGH*/
case DNS_AGAIN:
return DEFER; /* just defer this TLS'd conn */
+ case DNS_SUCCEED:
+ if (sec) return OK;
+ log_write(0, LOG_MAIN,
+ "DANE error: TLSA lookup for %s not DNSSEC", host->name);
+ /*FALLTRHOUGH*/
+
case DNS_NODATA: /* no TLSA RR for this lookup */
case DNS_NOMATCH: /* no records at all for this lookup */
return dane_required ? FAIL : FAIL_FORCED;