summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJeremy Harris <jgh146exb@wizmail.org>2014-09-02 13:14:01 +0100
committerJeremy Harris <jgh146exb@wizmail.org>2014-09-02 13:14:01 +0100
commit0f06b4f296802e4e13188c740ea09419931a3020 (patch)
tree958905a4af8487e55132b88e42ed237509c42f4e /src
parent2ce45f59d7f070fa3d96f88523a6e19a0e68aae2 (diff)
parentd567a64d80184840c08ca4a016a979233f09ec23 (diff)
Introduce EXPERIMENTAL_DANE feature
Diffstat (limited to 'src')
-rw-r--r--src/OS/Makefile-Base7
-rwxr-xr-xsrc/scripts/MakeLinks4
-rw-r--r--src/src/EDITME3
-rw-r--r--src/src/config.h.defaults1
-rw-r--r--src/src/dane-gnu.c21
-rw-r--r--src/src/dane-openssl.c1520
-rw-r--r--src/src/dane.c45
-rw-r--r--src/src/danessl.h31
-rw-r--r--src/src/deliver.c21
-rw-r--r--src/src/dns.c2
-rw-r--r--src/src/exim.c3
-rw-r--r--src/src/expand.c6
-rw-r--r--src/src/globals.c13
-rw-r--r--src/src/globals.h7
-rw-r--r--src/src/host.c51
-rw-r--r--src/src/spool_in.c5
-rw-r--r--src/src/structs.h3
-rw-r--r--src/src/tls-openssl.c343
-rw-r--r--src/src/transports/smtp.c39
-rw-r--r--src/src/transports/smtp.h4
-rw-r--r--src/src/verify.c22
21 files changed, 2078 insertions, 73 deletions
diff --git a/src/OS/Makefile-Base b/src/OS/Makefile-Base
index 87a803704..f82549ded 100644
--- a/src/OS/Makefile-Base
+++ b/src/OS/Makefile-Base
@@ -298,7 +298,7 @@ convert4r4: Makefile ../src/convert4r4.src
OBJ_WITH_CONTENT_SCAN = malware.o mime.o regex.o spam.o spool_mbox.o
OBJ_WITH_OLD_DEMIME = demime.o
-OBJ_EXPERIMENTAL = bmi_spam.o spf.o srs.o dcc.o dmarc.o
+OBJ_EXPERIMENTAL = bmi_spam.o spf.o srs.o dcc.o dmarc.o dane.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.
@@ -602,10 +602,11 @@ demime.o: $(HDRS) demime.c
# Dependencies for EXPERIMENTAL_* modules
bmi_spam.o: $(HDRS) bmi_spam.c
-spf.o: $(HDRS) spf.h spf.c
-srs.o: $(HDRS) srs.h srs.c
+dane.o: $(HDRS) dane.c dane-gnu.c dane-openssl.c
dcc.o: $(HDRS) dcc.h dcc.c
dmarc.o: $(HDRS) dmarc.h dmarc.c
+spf.o: $(HDRS) spf.h spf.c
+srs.o: $(HDRS) srs.h srs.c
# The module containing tables of available lookups, routers, auths, and
# transports must be rebuilt if any of them are. However, because the makefiles
diff --git a/src/scripts/MakeLinks b/src/scripts/MakeLinks
index 01cd21f1c..a50f75b36 100755
--- a/src/scripts/MakeLinks
+++ b/src/scripts/MakeLinks
@@ -271,6 +271,10 @@ ln -s ../src/srs.c srs.c
ln -s ../src/srs.h srs.h
ln -s ../src/dcc.c dcc.c
ln -s ../src/dcc.h dcc.h
+ln -s ../src/dane.c dane.c
+ln -s ../src/dane-gnu.c dane-gnu.c
+ln -s ../src/dane-openssl.c dane-openssl.c
+ln -s ../src/danessl.h danessl.h
# End of MakeLinks
diff --git a/src/src/EDITME b/src/src/EDITME
index d576fd7a3..01c4ebc9d 100644
--- a/src/src/EDITME
+++ b/src/src/EDITME
@@ -494,6 +494,9 @@ EXIM_MONITOR=eximon.bin
# Uncomment the following line to add DSN support
# EXPERIMENTAL_DSN=yes
+# Uncomment the following line to add DANE support
+# EXPERIMENTAL_DANE=yes
+
###############################################################################
# THESE ARE THINGS YOU MIGHT WANT TO SPECIFY #
###############################################################################
diff --git a/src/src/config.h.defaults b/src/src/config.h.defaults
index ba4615c11..49ab276ff 100644
--- a/src/src/config.h.defaults
+++ b/src/src/config.h.defaults
@@ -168,6 +168,7 @@ it's a default value. */
/* EXPERIMENTAL features */
#define EXPERIMENTAL_BRIGHTMAIL
#define EXPERIMENTAL_CERTNAMES
+#define EXPERIMENTAL_DANE
#define EXPERIMENTAL_DCC
#define EXPERIMENTAL_DMARC
#define EXPERIMENTAL_DSN
diff --git a/src/src/dane-gnu.c b/src/src/dane-gnu.c
new file mode 100644
index 000000000..b98bffad6
--- /dev/null
+++ b/src/src/dane-gnu.c
@@ -0,0 +1,21 @@
+/*************************************************
+* 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-openssl.c b/src/src/dane-openssl.c
new file mode 100644
index 000000000..6345b39ca
--- /dev/null
+++ b/src/src/dane-openssl.c
@@ -0,0 +1,1520 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+
+#include <openssl/opensslv.h>
+#include <openssl/err.h>
+#include <openssl/crypto.h>
+#include <openssl/safestack.h>
+#include <openssl/objects.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+#include <openssl/evp.h>
+
+#if OPENSSL_VERSION_NUMBER < 0x1000000fL
+# error "OpenSSL 1.0.0 or higher required"
+#else /* remainder of file */
+
+#include "danessl.h"
+
+#define DANE_F_ADD_SKID 100
+#define DANE_F_CHECK_END_ENTITY 101
+#define DANE_F_GROW_CHAIN 102
+#define DANE_F_LIST_ALLOC 103
+#define DANE_F_MATCH 104
+#define DANE_F_PUSH_EXT 105
+#define DANE_F_SET_TRUST_ANCHOR 106
+#define DANE_F_SSL_CTX_DANE_INIT 107
+#define DANE_F_SSL_DANE_ADD_TLSA 108
+#define DANE_F_SSL_DANE_INIT 109
+#define DANE_F_SSL_DANE_LIBRARY_INIT 110
+#define DANE_F_VERIFY_CERT 111
+#define DANE_F_WRAP_CERT 112
+
+#define DANE_R_BAD_CERT 100
+#define DANE_R_BAD_CERT_PKEY 101
+#define DANE_R_BAD_DATA_LENGTH 102
+#define DANE_R_BAD_DIGEST 103
+#define DANE_R_BAD_NULL_DATA 104
+#define DANE_R_BAD_PKEY 105
+#define DANE_R_BAD_SELECTOR 106
+#define DANE_R_BAD_USAGE 107
+#define DANE_R_DANE_INIT 108
+#define DANE_R_DANE_SUPPORT 109
+#define DANE_R_LIBRARY_INIT 110
+#define DANE_R_NOSIGN_KEY 111
+#define DANE_R_SCTX_INIT 112
+
+#ifndef OPENSSL_NO_ERR
+# define DANE_F_PLACEHOLDER 0 /* FIRST! Value TBD */
+static ERR_STRING_DATA dane_str_functs[] =
+{
+ {DANE_F_PLACEHOLDER, "DANE library"}, /* FIRST!!! */
+ {DANE_F_ADD_SKID, "add_skid"},
+ {DANE_F_CHECK_END_ENTITY, "check_end_entity"},
+ {DANE_F_GROW_CHAIN, "grow_chain"},
+ {DANE_F_LIST_ALLOC, "list_alloc"},
+ {DANE_F_MATCH, "match"},
+ {DANE_F_PUSH_EXT, "push_ext"},
+ {DANE_F_SET_TRUST_ANCHOR, "set_trust_anchor"},
+ {DANE_F_SSL_CTX_DANE_INIT, "SSL_CTX_dane_init"},
+ {DANE_F_SSL_DANE_ADD_TLSA, "SSL_dane_add_tlsa"},
+ {DANE_F_SSL_DANE_INIT, "SSL_dane_init"},
+ {DANE_F_SSL_DANE_LIBRARY_INIT, "SSL_dane_library_init"},
+ {DANE_F_VERIFY_CERT, "verify_cert"},
+ {DANE_F_WRAP_CERT, "wrap_cert"},
+ {0, NULL}
+};
+static ERR_STRING_DATA dane_str_reasons[] =
+{
+ {DANE_R_BAD_CERT, "Bad TLSA record certificate"},
+ {DANE_R_BAD_CERT_PKEY, "Bad TLSA record certificate public key"},
+ {DANE_R_BAD_DATA_LENGTH, "Bad TLSA record digest length"},
+ {DANE_R_BAD_DIGEST, "Bad TLSA record digest"},
+ {DANE_R_BAD_NULL_DATA, "Bad TLSA record null data"},
+ {DANE_R_BAD_PKEY, "Bad TLSA record public key"},
+ {DANE_R_BAD_SELECTOR, "Bad TLSA record selector"},
+ {DANE_R_BAD_USAGE, "Bad TLSA record usage"},
+ {DANE_R_DANE_INIT, "SSL_dane_init() required"},
+ {DANE_R_DANE_SUPPORT, "DANE library features not supported"},
+ {DANE_R_LIBRARY_INIT, "SSL_dane_library_init() required"},
+ {DANE_R_SCTX_INIT, "SSL_CTX_dane_init() required"},
+ {DANE_R_NOSIGN_KEY, "Certificate usage 2 requires EC support"},
+ {0, NULL}
+};
+#endif /*OPENSSL_NO_ERR*/
+
+#define DANEerr(f, r) ERR_PUT_error(err_lib_dane, (f), (r), __FILE__, __LINE__)
+
+static int err_lib_dane = -1;
+static int dane_idx = -1;
+
+#ifdef X509_V_FLAG_PARTIAL_CHAIN /* OpenSSL >= 1.0.2 */
+static int wrap_to_root = 0;
+#else
+static int wrap_to_root = 1;
+#endif
+
+static void (*cert_free)(void *) = (void (*)(void *)) X509_free;
+static void (*pkey_free)(void *) = (void (*)(void *)) EVP_PKEY_free;
+
+typedef struct dane_list
+{
+ struct dane_list *next;
+ void *value;
+} *dane_list;
+
+#define LINSERT(h, e) do { (e)->next = (h); (h) = (e); } while (0)
+
+typedef struct dane_host_list
+{
+ struct dane_host_list *next;
+ char *value;
+} *dane_host_list;
+
+typedef struct dane_data
+{
+ size_t datalen;
+ unsigned char data[0];
+} *dane_data;
+
+typedef struct dane_data_list
+{
+ struct dane_data_list *next;
+ dane_data value;
+} *dane_data_list;
+
+typedef struct dane_mtype
+{
+ int mdlen;
+ const EVP_MD *md;
+ dane_data_list data;
+} *dane_mtype;
+
+typedef struct dane_mtype_list
+{
+ struct dane_mtype_list *next;
+ dane_mtype value;
+} *dane_mtype_list;
+
+typedef struct dane_selector
+{
+ uint8_t selector;
+ dane_mtype_list mtype;
+} *dane_selector;
+
+typedef struct dane_selector_list
+{
+ struct dane_selector_list *next;
+ dane_selector value;
+} *dane_selector_list;
+
+typedef struct dane_pkey_list
+{
+ struct dane_pkey_list *next;
+ EVP_PKEY *value;
+} *dane_pkey_list;
+
+typedef struct dane_cert_list
+{
+ struct dane_cert_list *next;
+ X509 *value;
+} *dane_cert_list;
+
+typedef struct ssl_dane
+{
+ int (*verify)(X509_STORE_CTX *);
+ STACK_OF(X509) *roots;
+ STACK_OF(X509) *chain;
+ const char *thost; /* TLSA base domain */
+ char *mhost; /* Matched, peer name */
+ dane_pkey_list pkeys;
+ dane_cert_list certs;
+ dane_host_list hosts;
+ dane_selector_list selectors[SSL_DANE_USAGE_LAST + 1];
+ int depth;
+ int multi; /* Multi-label wildcards? */
+ int count; /* Number of TLSA records */
+} ssl_dane;
+
+#ifndef X509_V_ERR_HOSTNAME_MISMATCH
+# define X509_V_ERR_HOSTNAME_MISMATCH X509_V_ERR_APPLICATION_VERIFICATION
+#endif
+
+static int
+match(dane_selector_list slist, X509 *cert, int depth)
+{
+int matched;
+
+/*
+ * Note, set_trust_anchor() needs to know whether the match was for a
+ * pkey digest or a certificate digest. We return MATCHED_PKEY or
+ * MATCHED_CERT accordingly.
+ */
+#define MATCHED_CERT (SSL_DANE_SELECTOR_CERT + 1)
+#define MATCHED_PKEY (SSL_DANE_SELECTOR_SPKI + 1)
+
+/*
+ * Loop over each selector, mtype, and associated data element looking
+ * for a match.
+ */
+for(matched = 0; !matched && slist; slist = slist->next)
+ {
+ dane_mtype_list m;
+ unsigned char mdbuf[EVP_MAX_MD_SIZE];
+ unsigned char *buf = NULL;
+ unsigned char *buf2;
+ unsigned int len = 0;
+
+ /*
+ * Extract ASN.1 DER form of certificate or public key.
+ */
+ switch(slist->value->selector)
+ {
+ case SSL_DANE_SELECTOR_CERT:
+ len = i2d_X509(cert, NULL);
+ buf2 = buf = (unsigned char *) OPENSSL_malloc(len);
+ if(buf) i2d_X509(cert, &buf2);
+ break;
+ case SSL_DANE_SELECTOR_SPKI:
+ len = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert), NULL);
+ buf2 = buf = (unsigned char *) OPENSSL_malloc(len);
+ if(buf) i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert), &buf2);
+ break;
+ }
+
+ if(!buf)
+ {
+ DANEerr(DANE_F_MATCH, ERR_R_MALLOC_FAILURE);
+ return 0;
+ }
+ OPENSSL_assert(buf2 - buf == len);
+
+ /*
+ * Loop over each mtype and data element
+ */
+ for(m = slist->value->mtype; !matched && m; m = m->next)
+ {
+ dane_data_list d;
+ unsigned char *cmpbuf = buf;
+ unsigned int cmplen = len;
+
+ /*
+ * If it is a digest, compute the corresponding digest of the
+ * DER data for comparison, otherwise, use the full object.
+ */
+ if(m->value->md)
+ {
+ cmpbuf = mdbuf;
+ if(!EVP_Digest(buf, len, cmpbuf, &cmplen, m->value->md, 0))
+ matched = -1;
+ }
+ for(d = m->value->data; !matched && d; d = d->next)
+ if( cmplen == d->value->datalen
+ && memcmp(cmpbuf, d->value->data, cmplen) == 0)
+ matched = slist->value->selector + 1;
+ }
+
+ OPENSSL_free(buf);
+ }
+
+return matched;
+}
+
+static int
+push_ext(X509 *cert, X509_EXTENSION *ext)
+{
+X509_EXTENSIONS *exts;
+
+if(ext)
+ {
+ if(!(exts = cert->cert_info->extensions))
+ exts = cert->cert_info->extensions = sk_X509_EXTENSION_new_null();
+ if (exts && sk_X509_EXTENSION_push(exts, ext))
+ return 1;
+ X509_EXTENSION_free(ext);
+ }
+DANEerr(DANE_F_PUSH_EXT, ERR_R_MALLOC_FAILURE);
+return 0;
+}
+
+static int
+add_ext(X509 *issuer, X509 *subject, int ext_nid, char *ext_val)
+{
+X509V3_CTX v3ctx;
+
+X509V3_set_ctx(&v3ctx, issuer, subject, 0, 0, 0);
+return push_ext(subject, X509V3_EXT_conf_nid(0, &v3ctx, ext_nid, ext_val));
+}
+
+static int
+set_serial(X509 *cert, AUTHORITY_KEYID *akid, X509 *subject)
+{
+int ret = 0;
+BIGNUM *bn;
+
+if(akid && akid->serial)
+ return (X509_set_serialNumber(cert, akid->serial));
+
+/*
+ * Add one to subject's serial to avoid collisions between TA serial and
+ * serial of signing root.
+ */
+if( (bn = ASN1_INTEGER_to_BN(X509_get_serialNumber(subject), 0)) != 0
+ && BN_add_word(bn, 1)
+ && BN_to_ASN1_INTEGER(bn, X509_get_serialNumber(cert)))
+ ret = 1;
+
+if(bn)
+ BN_free(bn);
+return ret;
+}
+
+static int
+add_akid(X509 *cert, AUTHORITY_KEYID *akid)
+{
+int nid = NID_authority_key_identifier;
+ASN1_STRING *id;
+unsigned char c = 0;
+int ret = 0;
+
+/*
+ * 0 will never be our subject keyid from a SHA-1 hash, but it could be
+ * our subject keyid if forced from child's akid. If so, set our
+ * authority keyid to 1. This way we are never self-signed, and thus
+ * exempt from any potential (off by default for now in OpenSSL)
+ * self-signature checks!
+ */
+id = (ASN1_STRING *) ((akid && akid->keyid) ? akid->keyid : 0);
+if(id && M_ASN1_STRING_length(id) == 1 && *M_ASN1_STRING_data(id) == c)
+ c = 1;
+
+if( (akid = AUTHORITY_KEYID_new()) != 0
+ && (akid->keyid = ASN1_OCTET_STRING_new()) != 0
+ && M_ASN1_OCTET_STRING_set(akid->keyid, (void *) &c, 1)
+ && X509_add1_ext_i2d(cert, nid, akid, 0, X509V3_ADD_APPEND))
+ ret = 1;
+if(akid)
+ AUTHORITY_KEYID_free(akid);
+return ret;
+}
+
+static int
+add_skid(X509 *cert, AUTHORITY_KEYID *akid)
+{
+int nid = NID_subject_key_identifier;
+
+if(!akid || !akid->keyid)
+ return add_ext(0, cert, nid, "hash");
+return X509_add1_ext_i2d(cert, nid, akid->keyid, 0, X509V3_ADD_APPEND) > 0;
+}
+
+static X509_NAME *
+akid_issuer_name(AUTHORITY_KEYID *akid)
+{
+if(akid && akid->issuer)
+ {
+ int i;
+ GENERAL_NAMES *gens = akid->issuer;
+
+ for(i = 0; i < sk_GENERAL_NAME_num(gens); ++i)
+ {
+ GENERAL_NAME *gn = sk_GENERAL_NAME_value(gens, i);
+
+ if(gn->type == GEN_DIRNAME)
+ return (gn->d.dirn);
+ }
+ }
+return 0;
+}
+
+static int
+set_issuer_name(X509 *cert, AUTHORITY_KEYID *akid)
+{
+X509_NAME *name = akid_issuer_name(akid);
+
+/*
+ * If subject's akid specifies an authority key identifer issuer name, we
+ * must use that.
+ */
+return X509_set_issuer_name(cert,
+ name ? name : X509_get_subject_name(cert));
+}
+
+static int
+grow_chain(ssl_dane *dane, int trusted, X509 *cert)
+{
+STACK_OF(X509) **xs = trusted ? &dane->roots : &dane->chain;
+static ASN1_OBJECT *serverAuth = 0;
+
+#define UNTRUSTED 0
+#define TRUSTED 1
+
+if( trusted && !serverAuth
+ && !(serverAuth = OBJ_nid2obj(NID_server_auth)))
+ {
+ DANEerr(DANE_F_GROW_CHAIN, ERR_R_MALLOC_FAILURE);
+ return 0;
+ }
+if(!*xs && !(*xs = sk_X509_new_null()))
+ {
+ DANEerr(DANE_F_GROW_CHAIN, ERR_R_MALLOC_FAILURE);
+ return 0;
+ }
+
+if(cert)
+ {
+ if(trusted && !X509_add1_trust_object(cert, serverAuth))
+ return 0;
+ CRYPTO_add(&cert->references, 1, CRYPTO_LOCK_X509);
+ if (!sk_X509_push(*xs, cert))
+ {
+ X509_free(cert);
+ DANEerr(DANE_F_GROW_CHAIN, ERR_R_MALLOC_FAILURE);
+ return 0;
+ }
+ }
+return 1;
+}
+
+static int
+wrap_issuer(ssl_dane *dane, EVP_PKEY *key, X509 *subject, int depth, int top)
+{
+int ret = 1;
+X509 *cert = 0;
+AUTHORITY_KEYID *akid;
+X509_NAME *name = X509_get_issuer_name(subject);
+EVP_PKEY *newkey = key ? key : X509_get_pubkey(subject);
+
+#define WRAP_MID 0 /* Ensure intermediate. */
+#define WRAP_TOP 1 /* Ensure self-signed. */
+
+if(!name || !newkey || !(cert = X509_new()))
+ return 0;
+
+/*
+ * Record the depth of the trust-anchor certificate.
+ */
+if(dane->depth < 0)
+ dane->depth = depth + 1;
+
+/*
+ * XXX: Uncaught error condition:
+ *
+ * The return value is NULL both when the extension is missing, and when
+ * OpenSSL rans out of memory while parsing the extension.
+ */
+ERR_clear_error();
+akid = X509_get_ext_d2i(subject, NID_authority_key_identifier, 0, 0);
+/* XXX: Should we peek at the error stack here??? */
+
+/*
+ * If top is true generate a self-issued root CA, otherwise an
+ * intermediate CA and possibly its self-signed issuer.
+ *
+ * CA cert valid for +/- 30 days
+ */
+if( !X509_set_version(cert, 2)
+ || !set_serial(cert, akid, subject)
+ || !X509_set_subject_name(cert, name)
+ || !set_issuer_name(cert, akid)
+ || !X509_gmtime_adj(X509_get_notBefore(cert), -30 * 86400L)
+ || !X509_gmtime_adj(X509_get_notAfter(cert), 30 * 86400L)
+ || !X509_set_pubkey(cert, newkey)
+ || !add_ext(0, cert, NID_basic_constraints, "CA:TRUE")
+ || (!top && !add_akid(cert, akid))
+ || !add_skid(cert, akid)
+ || ( !top && wrap_to_root
+ && !wrap_issuer(dane, newkey, cert, depth, WRAP_TOP)))
+ ret = 0;
+
+if(akid)
+ AUTHORITY_KEYID_free(akid);
+if(!key)
+ EVP_PKEY_free(newkey);
+if(ret)
+ ret = grow_chain(dane, !top && wrap_to_root ? UNTRUSTED : TRUSTED, cert);
+if(cert)
+ X509_free(cert);
+return ret;
+}
+
+static int
+wrap_cert(ssl_dane *dane, X509 *tacert, int depth)
+{
+if(dane->depth < 0)
+ dane->depth = depth + 1;
+
+/*
+ * If the TA certificate is self-issued, or need not be, use it directly.
+ * Otherwise, synthesize requisuite ancestors.
+ */
+if( !wrap_to_root
+ || X509_check_issued(tacert, tacert) == X509_V_OK)
+ return grow_chain(dane, TRUSTED, tacert);
+
+if(wrap_issuer(dane, 0, tacert, depth, WRAP_MID))
+ return grow_chain(dane, UNTRUSTED, tacert);
+return 0;
+}
+
+static int
+ta_signed(ssl_dane *dane, X509 *cert, int depth)
+{
+dane_cert_list x;
+dane_pkey_list k;
+EVP_PKEY *pk;
+int done = 0;
+
+/*
+ * First check whether issued and signed by a TA cert, this is cheaper
+ * than the bare-public key checks below, since we can determine whether
+ * the candidate TA certificate issued the certificate to be checked
+ * first (name comparisons), before we bother with signature checks
+ * (public key operations).
+ */
+for (x = dane->certs; !done && x; x = x->next)
+ {
+ if(X509_check_issued(x->value, cert) == X509_V_OK)
+ {
+ if(!(pk = X509_get_pubkey(x->value)))
+ {
+ /*
+ * The cert originally contained a valid pkey, which does
+ * not just vanish, so this is most likely a memory error.
+ */
+ done = -1;
+ break;
+ }
+ /* Check signature, since some other TA may work if not this. */
+ if(X509_verify(cert, pk) > 0)
+ done = wrap_cert(dane, x->value, depth) ? 1 : -1;
+ EVP_PKEY_free(pk);
+ }
+ }
+
+/*
+ * With bare TA public keys, we can't check whether the trust chain is
+ * issued by the key, but we can determine whether it is signed by the
+ * key, so we go with that.
+ *
+ * Ideally, the corresponding certificate was presented in the chain, and we
+ * matched it by its public key digest one level up. This code is here
+ * to handle adverse conditions imposed by sloppy administrators of
+ * receiving systems with poorly constructed chains.
+ *
+ * We'd like to optimize out keys that should not match when the cert's
+ * authority key id does not match the key id of this key computed via
+ * the RFC keyid algorithm (SHA-1 digest of public key bit-string sans
+ * ASN1 tag and length thus also excluding the unused bits field that is
+ * logically part of the length). However, some CAs have a non-standard
+ * authority keyid, so we lose. Too bad.
+ *
+ * This may push errors onto the stack when the certificate signature is
+ * not of the right type or length, throw these away,
+ */
+for(k = dane->pkeys; !done && k; k = k->next)
+ if(X509_verify(cert, k->value) > 0)
+ done = wrap_issuer(dane, k->value, cert, depth, WRAP_MID) ? 1 : -1;
+ else
+ ERR_clear_error();
+
+return done;
+}
+
+static int
+set_trust_anchor(X509_STORE_CTX *ctx, ssl_dane *dane, X509 *cert)
+{
+int matched = 0;
+int n;
+int i;
+int depth = 0;
+EVP_PKEY *takey;
+X509 *ca;
+STACK_OF(X509) *in = ctx->untrusted; /* XXX: Accessor? */
+
+if(!grow_chain(dane, UNTRUSTED, 0))
+ return -1;
+
+/*
+ * Accept a degenerate case: depth 0 self-signed trust-anchor.
+ */
+if(X509_check_issued(cert, cert) == X509_V_OK)
+ {
+ dane->depth = 0;
+ matched = match(dane->selectors[SSL_DANE_USAGE_TRUSTED_CA], cert, 0);
+ if(matched > 0 && !grow_chain(dane, TRUSTED, cert))
+ matched = -1;
+ return matched;
+ }
+
+/* Make a shallow copy of the input untrusted chain. */
+if(!(in = sk_X509_dup(in)))
+ {
+ DANEerr(DANE_F_SET_TRUST_ANCHOR, ERR_R_MALLOC_FAILURE);
+ return -1;
+ }
+
+/*
+ * At each iteration we consume the issuer of the current cert. This
+ * reduces the length of the "in" chain by one. If no issuer is found,
+ * we are done. We also stop when a certificate matches a TA in the
+ * peer's TLSA RRset.
+ *
+ * Caller ensures that the initial certificate is not self-signed.
+ */
+for(n = sk_X509_num(in); n > 0; --n, ++depth)
+ {
+ for(i = 0; i < n; ++i)
+ if(X509_check_issued(sk_X509_value(in, i), cert) == X509_V_OK)
+ break;
+
+ /*
+ * Final untrusted element with no issuer in the peer's chain, it may
+ * however be signed by a pkey or cert obtained via a TLSA RR.
+ */
+ if(i == n)
+ break;
+
+ /* Peer's chain contains an issuer ca. */
+ ca = sk_X509_delete(in, i);
+
+ /* If not a trust anchor, record untrusted ca and continue. */
+ if((matched = match(dane->selectors[SSL_DANE_USAGE_TRUSTED_CA], ca, depth+1))
+ == 0)
+ {
+ if(grow_chain(dane, UNTRUSTED, ca))
+ {
+ if(!X509_check_issued(ca, ca) == X509_V_OK)
+ {
+ /* Restart with issuer as subject */
+ cert = ca;
+ continue;
+ }
+ /* Final self-signed element, skip ta_signed() check. */
+ cert = 0;
+ }
+ else
+ matched = -1;
+ }
+ else if(matched == MATCHED_CERT)
+ {
+ if(!wrap_cert(dane, ca, depth))
+ matched = -1;
+ }
+ else if(matched == MATCHED_PKEY)
+ {
+ if( !(takey = X509_get_pubkey(ca))
+ || !wrap_issuer(dane, takey, cert, depth, WRAP_MID))
+ {
+ if(takey)
+ EVP_PKEY_free(takey);
+ else
+ DANEerr(DANE_F_SET_TRUST_ANCHOR, ERR_R_MALLOC_FAILURE);
+ matched = -1;
+ }
+ }
+ break;
+ }
+
+/* Shallow free the duplicated input untrusted chain. */
+sk_X509_free(in);
+
+/*
+ * When the loop exits, if "cert" is set, it is not self-signed and has
+ * no issuer in the chain, we check for a possible signature via a DNS
+ * obtained TA cert or public key.
+ */
+if(matched == 0 && cert)
+ matched = ta_signed(dane, cert, depth);
+
+return matched;
+}
+
+static int
+check_end_entity(X509_STORE_CTX *ctx, ssl_dane *dane, X509 *cert)
+{
+int matched;
+
+matched = match(dane->selectors[SSL_DANE_USAGE_FIXED_LEAF], cert, 0);
+if(matched > 0)
+ if(!ctx->chain)
+ {
+ if( (ctx->chain = sk_X509_new_null())
+ && sk_X509_push(ctx->chain, cert))
+ CRYPTO_add(&cert->references, 1, CRYPTO_LOCK_X509);
+ else
+ {
+ DANEerr(DANE_F_CHECK_END_ENTITY, ERR_R_MALLOC_FAILURE);
+ return -1;
+ }
+ }
+return matched;
+}
+
+static int
+match_name(const char *certid, ssl_dane *dane)
+{
+int multi = dane->multi;
+dane_host_list hosts;
+
+for(hosts = dane->hosts; hosts; hosts = hosts->next)
+ {
+ int match_subdomain = 0;
+ const char *domain = hosts->value;
+ const char *parent;
+ int idlen;
+ int domlen;
+
+ if(*domain == '.' && domain[1] != '\0')
+ {
+ ++domain;
+ match_subdomain = 1;
+ }
+
+ /*
+ * Sub-domain match: certid is any sub-domain of hostname.
+ */
+ if(match_subdomain)
+ {
+ if( (idlen = strlen(certid)) > (domlen = strlen(domain)) + 1
+ && certid[idlen - domlen - 1] == '.'
+ && !strcasecmp(certid + (idlen - domlen), domain))
+ return 1;
+ else
+ continue;
+ }
+
+ /*
+ * Exact match and initial "*" match. The initial "*" in a certid
+ * matches one (if multi is false) or more hostname components under
+ * the condition that the certid contains multiple hostname components.
+ */
+ if( !strcasecmp(certid, domain)
+ || ( certid[0] == '*' && certid[1] == '.' && certid[2] != 0
+ && (parent = strchr(domain, '.')) != 0
+ && (idlen = strlen(certid + 1)) <= (domlen = strlen(parent))
+ && strcasecmp(multi ? parent + domlen - idlen : parent, certid+1) == 0))
+ return 1;
+ }
+return 0;
+}
+
+static char *
+check_name(char *name, int len)
+{
+char *cp = name + len;
+
+while(len > 0 && !*--cp)
+ --len; /* Ignore trailing NULs */
+if(len <= 0)
+ return 0;
+for(cp = name; *cp; cp++)
+ {
+ char c = *cp;
+ if (!((c >= 'a' && c <= 'z') ||
+ (c >= '0' && c <= '9') ||
+ (c >= 'A' && c <= 'Z') ||
+ (c == '.' || c == '-') ||
+ (c == '*')))
+ return 0; /* Only LDH, '.' and '*' */
+ }
+if(cp - name != len) /* Guard against internal NULs */
+ return 0;
+return name;
+}
+
+static char *
+parse_dns_name(const GENERAL_NAME *gn)
+{
+if(gn->type != GEN_DNS)
+ return 0;
+if(ASN1_STRING_type(gn->d.ia5) != V_ASN1_IA5STRING)
+ return 0;
+return check_name((char *) ASN1_STRING_data(gn->d.ia5),
+ ASN1_STRING_length(gn->d.ia5));
+}
+
+static char *
+parse_subject_name(X509 *cert)
+{
+X509_NAME *name = X509_get_subject_name(cert);
+X509_NAME_ENTRY *entry;
+ASN1_STRING *entry_str;
+unsigned char *namebuf;
+int nid = NID_commonName;
+int len;
+int i;
+
+if(!name || (i = X509_NAME_get_index_by_NID(name, nid, -1)) < 0)
+ return 0;
+if(!(entry = X509_NAME_get_entry(name, i)))
+ return 0;
+if(!(entry_str = X509_NAME_ENTRY_get_data(entry)))
+ return 0;
+
+if((len = ASN1_STRING_to_UTF8(&namebuf, entry_str)) < 0)
+ return 0;
+if(len <= 0 || check_name((char *) namebuf, len) == 0)
+ {
+ OPENSSL_free(namebuf);
+ return 0;
+ }
+return (char *) namebuf;
+}
+
+static int
+name_check(ssl_dane *dane, X509 *cert)
+{
+int matched = 0;
+BOOL got_altname = FALSE;
+GENERAL_NAMES *gens;
+
+gens = X509_get_ext_d2i(cert, NID_subject_alt_name, 0, 0);
+if(gens)
+ {
+ int n = sk_GENERAL_NAME_num(gens);
+ int i;
+
+ for(i = 0; i < n; ++i)
+ {
+ const GENERAL_NAME *gn = sk_GENERAL_NAME_value(gens, i);
+ const char *certid;
+
+ if(gn->type != GEN_DNS)
+ continue;
+ got_altname = TRUE;
+ certid = parse_dns_name(gn);
+ if(certid && *certid)
+ {
+ if((matched = match_name(certid, dane)) == 0)
+ continue;
+ if(!(dane->mhost = OPENSSL_strdup(certid)))
+ matched = -1;
+ break;
+ }
+ }
+ GENERAL_NAMES_free(gens);
+ }
+
+/*
+ * XXX: Should the subjectName be skipped when *any* altnames are present,
+ * or only when DNS altnames are present?
+ */
+if(got_altname)
+ {
+ char *certid = parse_subject_name(cert);
+ if(certid != 0 && *certid && (matched = match_name(certid, dane)) != 0)
+ dane->mhost = certid; /* Already a copy */
+ }
+return matched;
+}
+
+static int
+verify_chain(X509_STORE_CTX *ctx)
+{
+dane_selector_list issuer_rrs;
+dane_selector_list leaf_rrs;
+int (*cb)(int, X509_STORE_CTX *) = ctx->verify_cb;
+int ssl_idx = SSL_get_ex_data_X509_STORE_CTX_idx();
+SSL *ssl = X509_STORE_CTX_get_ex_data(ctx, ssl_idx);
+ssl_dane *dane = SSL_get_ex_data(ssl, dane_idx);
+X509 *cert = ctx->cert; /* XXX: accessor? */
+int matched = 0;
+int chain_length = sk_X509_num(ctx->chain);
+
+DEBUG(D_tls) debug_printf("Dane verify-chain\n");
+
+issuer_rrs = dane->selectors[SSL_DANE_USAGE_LIMIT_ISSUER];
+leaf_rrs = dane->selectors[SSL_DANE_USAGE_LIMIT_LEAF];
+ctx->verify = dane->verify;
+
+if((matched = name_check(dane, cert)) < 0)
+ {
+ X509_STORE_CTX_set_error(ctx, X509_V_ERR_OUT_OF_MEM);
+ return 0;
+ }
+
+if(!matched)
+ {
+ ctx->error_depth = 0;
+ ctx->current_cert = cert;
+ X509_STORE_CTX_set_error(ctx, X509_V_ERR_HOSTNAME_MISMATCH);
+ if(!cb(0, ctx))
+ return 0;
+ }
+matched = 0;
+
+/*
+ * Satisfy at least one usage 0 or 1 constraint, unless we've already
+ * matched a usage 2 trust anchor.
+ *
+ * XXX: internal_verify() doesn't callback with top certs that are not
+ * self-issued. This should be fixed in a future OpenSSL.
+ */
+if(dane->roots && sk_X509_num(dane->roots))
+ {
+#ifndef NO_CALLBACK_WORKAROUND
+ X509 *top = sk_X509_value(ctx->chain, dane->depth);
+
+ if(X509_check_issued(top, top) != X509_V_OK)
+ {
+ ctx->error_depth = dane->depth;
+ ctx->current_cert = top;
+ if(!cb(1, ctx))
+ return 0;
+ }
+#endif
+ /* Pop synthetic trust-anchor ancestors off the chain! */
+ while (--chain_length > dane->depth)
+ X509_free(sk_X509_pop(ctx->chain));
+ }
+else if(issuer_rrs || leaf_rrs)
+ {
+ int n = chain_length;
+
+ /*
+ * Check for an EE match, then a CA match at depths > 0, and
+ * finally, if the EE cert is self-issued, for a depth 0 CA match.
+ */
+ if(leaf_rrs)
+ matched = match(leaf_rrs, cert, 0);
+ while(!matched && issuer_rrs && --n >= 0)
+ {
+ X509 *xn = sk_X509_value(ctx->chain, n);
+
+ if(n > 0 || X509_check_issued(xn, xn) == X509_V_OK)
+ matched = match(issuer_rrs, xn, n);
+ }
+
+ if(matched < 0)
+ {
+ X509_STORE_CTX_set_error(ctx, X509_V_ERR_OUT_OF_MEM);
+ return 0;
+ }
+
+ if(!matched)
+ {
+ ctx->current_cert = cert;
+ ctx->error_depth = 0;
+ X509_STORE_CTX_set_error(ctx, X509_V_ERR_CERT_UNTRUSTED);
+ if(!cb(0, ctx))
+ return 0;
+ }
+ }
+
+return ctx->verify(ctx);
+}
+
+static int
+verify_cert(X509_STORE_CTX *ctx, void *unused_ctx)
+{
+static int ssl_idx = -1;
+SSL *ssl;
+ssl_dane *dane;
+int (*cb)(int, X509_STORE_CTX *) = ctx->verify_cb;
+int matched;
+X509 *cert = ctx->cert; /* XXX: accessor? */
+
+DEBUG(D_tls) debug_printf("Dane verify-cert\n");
+
+if(ssl_idx < 0)
+ ssl_idx = SSL_get_ex_data_X509_STORE_CTX_idx();
+if(dane_idx < 0)
+ {
+ DANEerr(DANE_F_VERIFY_CERT, ERR_R_MALLOC_FAILURE);
+ return -1;
+ }
+
+ssl = X509_STORE_CTX_get_ex_data(ctx, ssl_idx);
+if(!(dane = SSL_get_ex_data(ssl, dane_idx)) || !cert)
+ return X509_verify_cert(ctx);
+
+if(dane->selectors[SSL_DANE_USAGE_FIXED_LEAF])
+ {
+ if((matched = check_end_entity(ctx, dane, cert)) > 0)
+ {
+ ctx->error_depth = 0;
+ ctx->current_cert = cert;
+ return cb(1, ctx);
+ }
+ if(matched < 0)
+ {
+ X509_STORE_CTX_set_error(ctx, X509_V_ERR_OUT_OF_MEM);
+ return -1;
+ }
+ }
+
+if(dane->selectors[SSL_DANE_USAGE_TRUSTED_CA])
+ {
+ if((matched = set_trust_anchor(ctx, dane, cert)) < 0)
+ {
+ X509_STORE_CTX_set_error(ctx, X509_V_ERR_OUT_OF_MEM);
+ return -1;
+ }
+ if(matched)
+ {
+ /*
+ * Check that setting the untrusted chain updates the expected
+ * structure member at the expected offset.
+ */
+ X509_STORE_CTX_trusted_stack(ctx, dane->roots);
+ X509_STORE_CTX_set_chain(ctx, dane->chain);
+ OPENSSL_assert(ctx->untrusted == dane->chain);
+ }
+ }
+
+/*
+ * Name checks and usage 0/1 constraint enforcement are delayed until
+ * X509_verify_cert() builds the full chain and calls our verify_chain()
+ * wrapper.
+ */
+dane->verify = ctx->verify;
+ctx->verify = verify_chain;
+
+return X509_verify_cert(ctx);
+}
+
+static dane_list
+list_alloc(size_t vsize)
+{
+void *value = (void *) OPENSSL_malloc(vsize);
+dane_list l;
+
+if(!value)
+ {
+ DANEerr(DANE_F_LIST_ALLOC, ERR_R_MALLOC_FAILURE);
+ return 0;
+ }
+if(!(l = (dane_list) OPENSSL_malloc(sizeof(*l))))
+ {
+ OPENSSL_free(value);
+ DANEerr(DANE_F_LIST_ALLOC, ERR_R_MALLOC_FAILURE);
+ return 0;
+ }
+l->next = 0;
+l->value = value;
+return l;
+}
+
+static void
+list_free(void *list, void (*f)(void *))
+{
+dane_list head;
+dane_list next;
+
+for(head = (dane_list) list; head; head = next)
+ {
+ next = head->next;
+ if (f && head->value)
+ f(head->value);
+ OPENSSL_free(head);
+ }
+}
+
+static void
+dane_mtype_free(void *p)
+{
+list_free(((dane_mtype) p)->data, OPENSSL_freeFunc);
+OPENSSL_free(p);
+}
+
+static void
+dane_selector_free(void *p)
+{
+list_free(((dane_selector) p)->mtype, dane_mtype_free);
+OPENSSL_free(p);
+}
+
+
+
+/*
+
+Tidy up once the connection is finished with.
+
+Arguments
+ ssl The ssl connection handle
+
+=> Before calling SSL_free()
+tls_close() and tls_getc() [the error path] are the obvious places.
+Could we do it earlier - right after verification? In tls_client_start()
+right after SSL_connect() returns, in that case.
+
+*/
+
+void
+DANESSL_cleanup(SSL *ssl)
+{
+ssl_dane *dane;
+int u;
+
+DEBUG(D_tls) debug_printf("Dane lib-cleanup\n");
+
+if(dane_idx < 0 || !(dane = SSL_get_ex_data(ssl, dane_idx)))
+ return;
+(void) SSL_set_ex_data(ssl, dane_idx, 0);
+
+if(dane->hosts)
+ list_free(dane->hosts, OPENSSL_freeFunc);
+if(dane->mhost)
+ OPENSSL_free(dane->mhost);
+for(u = 0; u <= SSL_DANE_USAGE_LAST; ++u)
+ if(dane->selectors[u])
+ list_free(dane->selectors[u], dane_selector_free);
+if(dane->pkeys)
+ list_free(dane->pkeys, pkey_free);
+if(dane->certs)
+ list_free(dane->certs, cert_free);
+if(dane->roots)
+ sk_X509_pop_free(dane->roots, X509_free);
+if(dane->chain)
+ sk_X509_pop_free(dane->chain, X509_free);
+OPENSSL_free(dane);
+}
+
+static dane_host_list
+host_list_init(const char **src)
+{
+dane_host_list head = NULL;
+
+while(*src)
+ {
+ dane_host_list elem = (dane_host_list) OPENSSL_malloc(sizeof(*elem));
+ if(!elem)
+ {
+ list_free(head, OPENSSL_freeFunc);
+ return 0;
+ }
+ elem->value = OPENSSL_strdup(*src++);
+ LINSERT(head, elem);
+ }
+return head;
+}
+
+
+
+
+/*
+
+Call this for each TLSA record found for the target, after the
+DANE setup has been done on the ssl connection handle.
+
+Arguments:
+ ssl Connection handle
+ usage TLSA record field
+ selector TLSA record field
+ mdname ??? message digest name?
+ data ??? TLSA record megalump?
+ dlen length of data
+
+Return
+ -1 on error
+ 0 action not taken
+ 1 record accepted
+*/
+
+int
+DANESSL_add_tlsa(SSL *ssl, uint8_t usage, uint8_t selector, const char *mdname,
+ unsigned const char *data, size_t dlen)
+{
+ssl_dane *dane;
+dane_selector_list s = 0;
+dane_mtype_list m = 0;
+dane_data_list d = 0;
+dane_cert_list xlist = 0;
+dane_pkey_list klist = 0;
+const EVP_MD *md = 0;
+
+DEBUG(D_tls) debug_printf("Dane add-tlsa: usage %u sel %u mdname \"%s\"\n",
+ usage, selector, mdname);
+
+if(dane_idx < 0 || !(dane = SSL_get_ex_data(ssl, dane_idx)))
+ {
+ DANEerr(DANE_F_SSL_DANE_ADD_TLSA, DANE_R_DANE_INIT);
+ return -1;
+ }
+
+if(usage > SSL_DANE_USAGE_LAST)
+ {
+ DANEerr(DANE_F_SSL_DANE_ADD_TLSA, DANE_R_BAD_USAGE);
+ return 0;
+ }
+if(selector > SSL_DANE_SELECTOR_LAST)
+ {
+ DANEerr(DANE_F_SSL_DANE_ADD_TLSA, DANE_R_BAD_SELECTOR);
+ return 0;
+ }
+if(mdname && !(md = EVP_get_digestbyname(mdname)))
+ {
+ DANEerr(DANE_F_SSL_DANE_ADD_TLSA, DANE_R_BAD_DIGEST);
+ return 0;
+ }
+if(!data)
+ {
+ DANEerr(DANE_F_SSL_DANE_ADD_TLSA, DANE_R_BAD_NULL_DATA);
+ return 0;
+ }
+if(mdname && dlen != EVP_MD_size(md))
+ {
+ DANEerr(DANE_F_SSL_DANE_ADD_TLSA, DANE_R_BAD_DATA_LENGTH);
+ return 0;
+ }
+
+if(!mdname)
+ {
+ X509 *x = 0;
+ EVP_PKEY *k = 0;
+ const unsigned char *p = data;
+
+#define xklistinit(lvar, ltype, var, freeFunc) do { \
+ (lvar) = (ltype) OPENSSL_malloc(sizeof(*(lvar))); \
+ if (!(lvar)) { \
+ DANEerr(DANE_F_SSL_DANE_ADD_TLSA, ERR_R_MALLOC_FAILURE); \
+ freeFunc((var)); \
+ return 0; \
+ } \
+ (lvar)->next = 0; \
+ lvar->value = var; \
+ } while (0)
+#define xkfreeret(ret) do { \
+ if (xlist) list_free(xlist, cert_free); \
+ if (klist) list_free(klist, pkey_free); \
+ return (ret); \
+ } while (0)
+
+ switch(selector)
+ {
+ case SSL_DANE_SELECTOR_CERT:
+ if(!d2i_X509(&x, &p, dlen) || dlen != p - data)
+ {
+ if (x)
+ X509_free(x);
+ DANEerr(DANE_F_SSL_DANE_ADD_TLSA, DANE_R_BAD_CERT);
+ return 0;
+ }
+ k = X509_get_pubkey(x);
+ EVP_PKEY_free(k);
+ if(!k)
+ {
+ X509_free(x);
+ DANEerr(DANE_F_SSL_DANE_ADD_TLSA, DANE_R_BAD_CERT_PKEY);
+ return 0;
+ }
+ if(usage == SSL_DANE_USAGE_TRUSTED_CA)
+ xklistinit(xlist, dane_cert_list, x, X509_free);
+ break;
+
+ case SSL_DANE_SELECTOR_SPKI:
+ if(!d2i_PUBKEY(&k, &p, dlen) || dlen != p - data)
+ {
+ if(k)
+ EVP_PKEY_free(k);
+ DANEerr(DANE_F_SSL_DANE_ADD_TLSA, DANE_R_BAD_PKEY);
+ return 0;
+ }
+ if(usage == SSL_DANE_USAGE_TRUSTED_CA)
+ xklistinit(klist, dane_pkey_list, k, EVP_PKEY_free);
+ break;
+ }
+ }
+
+/* Find insertion point and don't add duplicate elements. */
+for(s = dane->selectors[usage]; s; s = s->next)
+ if(s->value->selector == selector)
+ for(m = s->value->mtype; m; m = m->next)
+ if(m->value->md == md)
+ for(d = m->value->data; d; d = d->next)
+ if( d->value->datalen == dlen
+ && memcmp(d->value->data, data, dlen) == 0)
+ xkfreeret(1);
+
+if(!(d = (dane_data_list) list_alloc(sizeof(*d->value) + dlen)))
+ xkfreeret(0);
+d->value->datalen = dlen;
+memcpy(d->value->data, data, dlen);
+if(!m)
+ {
+ if(!(m = (dane_mtype_list) list_alloc(sizeof(*m->value))))
+ {
+ list_free(d, OPENSSL_freeFunc);
+ xkfreeret(0);
+ }
+ m->value->data = 0;
+ if((m->value->md = md) != 0)
+ m->value->mdlen = dlen;
+ if(!s)
+ {
+ if(!(s = (dane_selector_list) list_alloc(sizeof(*s->value))))
+ {
+ list_free(m, dane_mtype_free);
+ xkfreeret(0);
+ }
+ s->value->mtype = 0;
+ s->value->selector = selector;
+ LINSERT(dane->selectors[usage], s);
+ }
+ LINSERT(s->value->mtype, m);
+ }
+LINSERT(m->value->data, d);
+
+if(xlist)
+ LINSERT(dane->certs, xlist);
+else if(klist)
+ LINSERT(dane->pkeys, klist);
+++dane->count;
+return 1;
+}
+
+
+
+
+/*
+Call this once we have an ssl connection handle but before
+making the TLS connection.
+
+=> In tls_client_start() after the call to SSL_new()
+and before the call to SSL_connect(). Exactly where
+probably does not matter.
+We probably want to keep our existing SNI handling;
+call this with NULL.
+
+Arguments:
+ ssl Connection handle
+ sni_domain Optional peer server name
+ hostnames list of names to chack against peer cert
+
+Return
+ -1 on fatal error
+ 0 nonfatal error
+ 1 success
+*/
+
+int
+DANESSL_init(SSL *ssl, const char *sni_domain, const char **hostnames)
+{
+ssl_dane *dane;
+int i;
+#ifdef OPENSSL_INTERNAL
+SSL_CTX *sctx = SSL_get_SSL_CTX(ssl);
+
+
+if(sctx->app_verify_callback != verify_cert)
+ {
+ DANEerr(DANE_F_SSL_DANE_INIT, DANE_R_SCTX_INIT);
+ return -1;
+ }
+#else
+DEBUG(D_tls) debug_printf("Dane ssl-init\n");
+if(dane_idx < 0)
+ {
+ DANEerr(DANE_F_SSL_DANE_INIT, DANE_R_LIBRARY_INIT);
+ return -1;
+ }
+#endif
+
+if(sni_domain && !SSL_set_tlsext_host_name(ssl, sni_domain))
+ return 0;
+
+if(!(dane = (ssl_dane *) OPENSSL_malloc(sizeof(ssl_dane))))
+ {
+ DANEerr(DANE_F_SSL_DANE_INIT, ERR_R_MALLOC_FAILURE);
+ return 0;
+ }
+if(!SSL_set_ex_data(ssl, dane_idx, dane))
+ {
+ DANEerr(DANE_F_SSL_DANE_INIT, ERR_R_MALLOC_FAILURE);
+ OPENSSL_free(dane);
+ return 0;
+ }
+
+dane->verify = 0;
+dane->hosts = 0;
+dane->thost = 0;
+dane->pkeys = 0;
+dane->certs = 0;
+dane->chain = 0;
+dane->roots = 0;
+dane->depth = -1;
+dane->mhost = 0; /* Future SSL control interface */
+dane->multi = 0; /* Future SSL control interface */
+dane->count = 0;
+
+for(i = 0; i <= SSL_DANE_USAGE_LAST; ++i)
+ dane->selectors[i] = 0;
+
+if(hostnames && !(dane->hosts = host_list_init(hostnames)))
+ {
+ DANEerr(DANE_F_SSL_DANE_INIT, ERR_R_MALLOC_FAILURE);
+ DANESSL_cleanup(ssl);
+ return 0;
+ }
+
+return 1;
+}
+
+
+/*
+
+Call this once we have a context to work with, but
+before DANESSL_init()
+
+=> in tls_client_start(), after tls_init() call gives us the ctx,
+if we decide we want to (policy) and can (TLSA records available)
+replacing (? what about fallback) everything from testing tls_verify_hosts
+down to just before calling SSL_new() for the conn handle.
+
+Arguments
+ ctx SSL context
+
+Return
+ -1 Error
+ 1 Success
+*/
+
+int
+DANESSL_CTX_init(SSL_CTX *ctx)
+{
+DEBUG(D_tls) debug_printf("Dane ctx-init\n");
+if(dane_idx >= 0)
+ {
+ SSL_CTX_set_cert_verify_callback(ctx, verify_cert, 0);
+ return 1;
+ }
+DANEerr(DANE_F_SSL_CTX_DANE_INIT, DANE_R_LIBRARY_INIT);
+return -1;
+}
+
+static int
+init_once(volatile int *value, int (*init)(void), void (*postinit)(void))
+{
+int wlock = 0;
+
+CRYPTO_r_lock(CRYPTO_LOCK_SSL_CTX);
+if(*value < 0)
+ {
+ CRYPTO_r_unlock(CRYPTO_LOCK_SSL_CTX);
+ CRYPTO_w_lock(CRYPTO_LOCK_SSL_CTX);
+ wlock = 1;
+ if(*value < 0)
+ {
+ *value = init();
+ if(postinit)
+ postinit();
+ }
+ }
+if (wlock)
+ CRYPTO_w_unlock(CRYPTO_LOCK_SSL_CTX);
+else
+ CRYPTO_r_unlock(CRYPTO_LOCK_SSL_CTX);
+return *value;
+}
+
+static void
+dane_init(void)
+{
+/*
+ * Store library id in zeroth function slot, used to locate the library
+ * name. This must be done before we load the error strings.
+ */
+#ifndef OPENSSL_NO_ERR
+dane_str_functs[0].error |= ERR_PACK(err_lib_dane, 0, 0);
+ERR_load_strings(err_lib_dane, dane_str_functs);
+ERR_load_strings(err_lib_dane, dane_str_reasons);
+#endif
+
+/*
+ * Register SHA-2 digests, if implemented and not already registered.
+ */
+#if defined(LN_sha256) && defined(NID_sha256) && !defined(OPENSSL_NO_SHA256)
+if(!EVP_get_digestbyname(LN_sha224)) EVP_add_digest(EVP_sha224());
+if(!EVP_get_digestbyname(LN_sha256)) EVP_add_digest(EVP_sha256());
+#endif
+#if defined(LN_sha512) && defined(NID_sha512) && !defined(OPENSSL_NO_SHA512)
+if(!EVP_get_digestbyname(LN_sha384)) EVP_add_digest(EVP_sha384());
+if(!EVP_get_digestbyname(LN_sha512)) EVP_add_digest(EVP_sha512());
+#endif
+
+/*
+ * Register an SSL index for the connection-specific ssl_dane structure.
+ * Using a separate index makes it possible to add DANE support to
+ * existing OpenSSL releases that don't have a suitable pointer in the
+ * SSL structure.
+ */
+dane_idx = SSL_get_ex_new_index(0, 0, 0, 0, 0);
+}
+
+
+
+/*
+
+Call this once. Probably early in startup will do; may need
+to be after SSL library init.
+
+=> put after call to tls_init() for now
+
+Return
+ 1 Success
+ 0 Fail
+*/
+
+int
+DANESSL_library_init(void)
+{
+DEBUG(D_tls) debug_printf("Dane lib-init\n");
+if(err_lib_dane < 0)
+ init_once(&err_lib_dane, ERR_get_next_error_library, dane_init);
+
+#if defined(LN_sha256)
+/* No DANE without SHA256 support */
+if(dane_idx >= 0 && EVP_get_digestbyname(LN_sha256) != 0)
+ return 1;
+#endif
+
+DANEerr(DANE_F_SSL_DANE_LIBRARY_INIT, DANE_R_DANE_SUPPORT);
+return 0;
+}
+
+
+#endif /* OPENSSL_VERSION_NUMBER */
+/* vi: aw ai sw=2
+*/
diff --git a/src/src/dane.c b/src/src/dane.c
new file mode 100644
index 000000000..20dfe5b18
--- /dev/null
+++ b/src/src/dane.c
@@ -0,0 +1,45 @@
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2012, 2014 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* This module provides DANE (RFC6659) support for Exim. See also
+the draft RFC for DANE-over-SMTP, "SMTP security via opportunistic DANE TLS"
+(V. Dukhovni, W. Hardaker) - version 10, dated May 25, 2014.
+
+The code for DANE support with Openssl was provided by V.Dukhovni.
+
+No cryptographic code is included in Exim. All this module does is to call
+functions from the OpenSSL or GNU TLS libraries. */
+
+
+#include "exim.h"
+
+/* This module is compiled only when it is specifically requested in the
+build-time configuration. However, some compilers don't like compiling empty
+modules, so keep them happy with a dummy when skipping the rest. Make it
+reference itself to stop picky compilers complaining that it is unused, and put
+in a dummy argument to stop even pickier compilers complaining about infinite
+loops. */
+
+#ifndef EXPERIMENTAL_DANE
+static void dummy(int x) { dummy(x-1); }
+#else
+
+/* Enabling DANE without enabling TLS cannot work. Abort the compilation. */
+# ifndef SUPPORT_TLS
+# error DANE support requires that TLS support must be enabled. Abort build.
+# endif
+
+# ifdef USE_GNUTLS
+# include "dane-gnu.c"
+# else
+# include "dane-openssl.c"
+# endif
+
+
+#endif /* EXPERIMENTAL_DANE */
+
+/* End of dane.c */
diff --git a/src/src/danessl.h b/src/src/danessl.h
new file mode 100644
index 000000000..5b1584da2
--- /dev/null
+++ b/src/src/danessl.h
@@ -0,0 +1,31 @@
+#ifndef HEADER_SSL_DANE_H
+#define HEADER_SSL_DANE_H
+
+#include <stdint.h>
+#include <openssl/ssl.h>
+
+/*-
+ * Certificate usages:
+ * https://tools.ietf.org/html/rfc6698#section-2.1.1
+ */
+#define SSL_DANE_USAGE_LIMIT_ISSUER 0
+#define SSL_DANE_USAGE_LIMIT_LEAF 1
+#define SSL_DANE_USAGE_TRUSTED_CA 2
+#define SSL_DANE_USAGE_FIXED_LEAF 3
+#define SSL_DANE_USAGE_LAST SSL_DANE_USAGE_FIXED_LEAF
+
+/*-
+ * Selectors:
+ * https://tools.ietf.org/html/rfc6698#section-2.1.2
+ */
+#define SSL_DANE_SELECTOR_CERT 0
+#define SSL_DANE_SELECTOR_SPKI 1
+#define SSL_DANE_SELECTOR_LAST SSL_DANE_SELECTOR_SPKI
+
+extern int DANESSL_library_init(void);
+extern int DANESSL_CTX_init(SSL_CTX *);
+extern int DANESSL_init(SSL *, const char *, const char **);
+extern void DANESSL_cleanup(SSL *);
+extern int DANESSL_add_tlsa(SSL *, uint8_t, uint8_t, const char *,
+ unsigned const char *, size_t);
+#endif
diff --git a/src/src/deliver.c b/src/src/deliver.c
index b3a5a49b2..78b669ad2 100644
--- a/src/src/deliver.c
+++ b/src/src/deliver.c
@@ -699,7 +699,15 @@ d_tlslog(uschar * s, int * sizep, int * ptrp, address_item * addr)
if ((log_extra_selector & LX_tls_certificate_verified) != 0 &&
addr->cipher != NULL)
s = string_append(s, sizep, ptrp, 2, US" CV=",
- testflag(addr, af_cert_verified)? "yes":"no");
+ testflag(addr, af_cert_verified)
+ ?
+#ifdef EXPERIMENTAL_DANE
+ testflag(addr, af_dane_verified)
+ ? "dane"
+ :
+#endif
+ "yes"
+ : "no");
if ((log_extra_selector & LX_tls_peerdn) != 0 && addr->peerdn != NULL)
s = string_append(s, sizep, ptrp, 3, US" DN=\"",
string_printing(addr->peerdn), US"\"");
@@ -1139,6 +1147,9 @@ if (result == OK)
tls_out.cipher = addr->cipher;
tls_out.peerdn = addr->peerdn;
tls_out.ocsp = addr->ocsp;
+# ifdef EXPERIMENTAL_DANE
+ tls_out.dane_verified = testflag(addr, af_dane_verified);
+# endif
#endif
delivery_log(LOG_MAIN, addr, logchar, NULL);
@@ -1157,6 +1168,9 @@ if (result == OK)
tls_out.cipher = NULL;
tls_out.peerdn = NULL;
tls_out.ocsp = OCSP_NOT_REQ;
+# ifdef EXPERIMENTAL_DANE
+ tls_out.dane_verified = FALSE;
+# endif
#endif
}
@@ -4171,6 +4185,9 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
/* The certificate verification status goes into the flags */
if (tls_out.certificate_verified) setflag(addr, af_cert_verified);
+#ifdef EXPERIMENTAL_DANE
+ if (tls_out.dane_verified) setflag(addr, af_dane_verified);
+#endif
/* Use an X item only if there's something to send */
#ifdef SUPPORT_TLS
@@ -7056,12 +7073,14 @@ wording. */
{
struct stat statbuf;
if (fstat(deliver_datafile, &statbuf) == 0 && statbuf.st_size > max)
+ {
if (emf_text)
fprintf(f, "%s", CS emf_text);
else
fprintf(f,
"------ The body of the message is " OFF_T_FMT " characters long; only the first\n"
"------ %d or so are included here.\n", statbuf.st_size, max);
+ }
}
fputc('\n', f);
diff --git a/src/src/dns.c b/src/src/dns.c
index 6efb88d58..3d047abba 100644
--- a/src/src/dns.c
+++ b/src/src/dns.c
@@ -607,7 +607,7 @@ if (check_dns_names_pattern[0] != 0 && type != T_PTR && type != T_TXT)
/* For an SRV lookup, skip over the first two components (the service and
protocol names, which both start with an underscore). */
- if (type == T_SRV)
+ if (type == T_SRV || type == T_TLSA)
{
while (*checkname++ != '.');
while (*checkname++ != '.');
diff --git a/src/src/exim.c b/src/src/exim.c
index 51daa5576..85a7c812c 100644
--- a/src/src/exim.c
+++ b/src/src/exim.c
@@ -827,6 +827,9 @@ fprintf(f, "Support for:");
#ifdef EXPERIMENTAL_BRIGHTMAIL
fprintf(f, " Experimental_Brightmail");
#endif
+#ifdef EXPERIMENTAL_DANE
+ fprintf(f, " Experimental_DANE");
+#endif
#ifdef EXPERIMENTAL_DCC
fprintf(f, " Experimental_DCC");
#endif
diff --git a/src/src/expand.c b/src/src/expand.c
index f6b70cb47..8111c4212 100644
--- a/src/src/expand.c
+++ b/src/src/expand.c
@@ -685,6 +685,9 @@ static var_entry var_table[] = {
{ "tls_out_bits", vtype_int, &tls_out.bits },
{ "tls_out_certificate_verified", vtype_int,&tls_out.certificate_verified },
{ "tls_out_cipher", vtype_stringptr, &tls_out.cipher },
+#ifdef EXPERIMENTAL_DANE
+ { "tls_out_dane", vtype_bool, &tls_out.dane_verified },
+#endif
{ "tls_out_ocsp", vtype_int, &tls_out.ocsp },
{ "tls_out_ourcert", vtype_cert, &tls_out.ourcert },
{ "tls_out_peercert", vtype_cert, &tls_out.peercert },
@@ -692,6 +695,9 @@ static var_entry var_table[] = {
#if defined(SUPPORT_TLS)
{ "tls_out_sni", vtype_stringptr, &tls_out.sni },
#endif
+#ifdef EXPERIMENTAL_DANE
+ { "tls_out_tlsa_usage", vtype_int, &tls_out.tlsa_usage },
+#endif
{ "tls_peerdn", vtype_stringptr, &tls_in.peerdn }, /* mind the alphabetical order! */
#if defined(SUPPORT_TLS)
diff --git a/src/src/globals.c b/src/src/globals.c
index ef1c1fd02..22bd69e01 100644
--- a/src/src/globals.c
+++ b/src/src/globals.c
@@ -103,6 +103,10 @@ tls_support tls_in = {
-1, /* tls_active */
0, /* tls_bits */
FALSE,/* tls_certificate_verified */
+#ifdef EXPERIMENTAL_DANE
+ FALSE,/* dane_verified */
+ 0, /* tlsa_usage */
+#endif
NULL, /* tls_cipher */
FALSE,/* tls_on_connect */
NULL, /* tls_on_connect_ports */
@@ -116,6 +120,10 @@ tls_support tls_out = {
-1, /* tls_active */
0, /* tls_bits */
FALSE,/* tls_certificate_verified */
+#ifdef EXPERIMENTAL_DANE
+ FALSE,/* dane_verified */
+ 0, /* tlsa_usage */
+#endif
NULL, /* tls_cipher */
FALSE,/* tls_on_connect */
NULL, /* tls_on_connect_ports */
@@ -634,6 +642,9 @@ BOOL dmarc_enable_forensic = FALSE;
uschar *dns_again_means_nonexist = NULL;
int dns_csa_search_limit = 5;
BOOL dns_csa_use_reverse = TRUE;
+#ifdef EXPERIMENTAL_DANE
+int dns_dane_ok = -1;
+#endif
uschar *dns_ipv4_lookup = NULL;
int dns_retrans = 0;
int dns_retry = 0;
@@ -1385,7 +1396,7 @@ transport_instance transport_defaults = {
TRUE_UNSET /* retry_use_local_part: BOOL, but set neither
1 nor 0 so can detect unset */
#ifdef EXPERIMENTAL_TPDA
- ,NULL /* tpda_delivery_action */
+ ,NULL /* tpda_event_action */
#endif
};
diff --git a/src/src/globals.h b/src/src/globals.h
index 73793aa2e..800ec9c31 100644
--- a/src/src/globals.h
+++ b/src/src/globals.h
@@ -82,6 +82,10 @@ typedef struct {
int active; /* fd/socket when in a TLS session */
int bits; /* bits used in TLS session */
BOOL certificate_verified; /* Client certificate verified */
+#ifdef EXPERIMENTAL_DANE
+ BOOL dane_verified; /* ... via DANE */
+ int tlsa_usage; /* TLSA record(s) usage */
+#endif
uschar *cipher; /* Cipher used */
BOOL on_connect; /* For older MTAs that don't STARTTLS */
uschar *on_connect_ports; /* Ports always tls-on-connect */
@@ -386,6 +390,9 @@ extern uschar *dns_again_means_nonexist; /* Domains that are badly set up */
extern int dns_csa_search_limit; /* How deep to search for CSA SRV records */
extern BOOL dns_csa_use_reverse; /* Check CSA in reverse DNS? (non-standard) */
extern uschar *dns_ipv4_lookup; /* For these domains, don't look for AAAA (or A6) */
+#ifdef EXPERIMENTAL_DANE
+extern int dns_dane_ok; /* Ok to use DANE when checking TLS authenticity */
+#endif
extern int dns_retrans; /* Retransmission time setting */
extern int dns_retry; /* Number of retries */
extern int dns_dnssec_ok; /* When constructing DNS query, set DO flag */
diff --git a/src/src/host.c b/src/src/host.c
index 00524f416..2eef0ba70 100644
--- a/src/src/host.c
+++ b/src/src/host.c
@@ -2207,7 +2207,7 @@ Returns: HOST_FIND_FAILED couldn't find A record
static int
set_address_from_dns(host_item *host, host_item **lastptr,
uschar *ignore_target_hosts, BOOL allow_ip, uschar **fully_qualified_name,
- BOOL dnssec_requested, BOOL dnssec_require)
+ BOOL dnssec_request, BOOL dnssec_require)
{
dns_record *rr;
host_item *thishostlast = NULL; /* Indicates not yet filled in anything */
@@ -2268,7 +2268,7 @@ for (; i >= 0; i--)
dns_scan dnss;
int rc = dns_lookup(&dnsa, host->name, type, fully_qualified_name);
- lookup_dnssec_authenticated = !dnssec_requested ? NULL
+ lookup_dnssec_authenticated = !dnssec_request ? NULL
: dns_is_secure(&dnsa) ? US"yes" : US"no";
/* We want to return HOST_FIND_AGAIN if one of the A, A6, or AAAA lookups
@@ -2292,11 +2292,31 @@ for (; i >= 0; i--)
if (rc != DNS_NOMATCH && rc != DNS_NODATA) v6_find_again = TRUE;
continue;
}
- if (dnssec_require && !dns_is_secure(&dnsa))
+
+ if (dnssec_request)
{
- log_write(L_host_lookup_failed, LOG_MAIN, "dnssec fail on %s for %.256s",
+ if (dns_is_secure(&dnsa))
+ {
+ DEBUG(D_host_lookup) debug_printf("%s A DNSSEC\n", host->name);
+ if (host->dnssec == DS_UNK) /* set in host_find_bydns() */
+ host->dnssec = DS_YES;
+ }
+ else
+ {
+ if (dnssec_require)
+ {
+ log_write(L_host_lookup_failed, LOG_MAIN,
+ "dnssec fail on %s for %.256s",
i>1 ? "A6" : i>0 ? "AAAA" : "A", host->name);
- continue;
+ continue;
+ }
+ if (host->dnssec == DS_YES) /* set in host_find_bydns() */
+ {
+ DEBUG(D_host_lookup) debug_printf("%s A cancel DNSSEC\n", host->name);
+ host->dnssec = DS_NO;
+ lookup_dnssec_authenticated = US"no";
+ }
+ }
}
/* Lookup succeeded: fill in the given host item with the first non-ignored
@@ -2562,9 +2582,14 @@ if (rc != DNS_SUCCEED && (whichrrs & HOST_FIND_BY_MX) != 0)
if (dnssec_request)
{
if (dns_is_secure(&dnsa))
- { dnssec = DS_YES; lookup_dnssec_authenticated = US"yes"; }
+ {
+ DEBUG(D_host_lookup) debug_printf("%s MX DNSSEC\n", host->name);
+ dnssec = DS_YES; lookup_dnssec_authenticated = US"yes";
+ }
else
- { dnssec = DS_NO; lookup_dnssec_authenticated = US"no"; }
+ {
+ dnssec = DS_NO; lookup_dnssec_authenticated = US"no";
+ }
}
switch (rc)
@@ -2578,7 +2603,7 @@ if (rc != DNS_SUCCEED && (whichrrs & HOST_FIND_BY_MX) != 0)
log_write(L_host_lookup_failed, LOG_MAIN,
"dnssec fail on MX for %.256s", host->name);
rc = DNS_FAIL;
- /*FALLTRHOUGH*/
+ /*FALLTHROUGH*/
case DNS_FAIL:
case DNS_AGAIN:
@@ -2609,19 +2634,11 @@ if (rc != DNS_SUCCEED)
last = host; /* End of local chainlet */
host->mx = MX_NONE;
host->port = PORT_NONE;
- dnssec = DS_UNK;
+ host->dnssec = DS_UNK;
lookup_dnssec_authenticated = NULL;
rc = set_address_from_dns(host, &last, ignore_target_hosts, FALSE,
fully_qualified_name, dnssec_request, dnssec_require);
- if (dnssec_request)
- {
- if (dns_is_secure(&dnsa))
- { dnssec = DS_YES; lookup_dnssec_authenticated = US"yes"; }
- else
- { dnssec = DS_NO; lookup_dnssec_authenticated = US"no"; }
- }
-
/* If one or more address records have been found, check that none of them
are local. Since we know the host items all have their IP addresses
inserted, host_scan_for_local_hosts() can only return HOST_FOUND or
diff --git a/src/src/spool_in.c b/src/src/spool_in.c
index 6dcb512e4..bbb4da6aa 100644
--- a/src/src/spool_in.c
+++ b/src/src/spool_in.c
@@ -284,6 +284,9 @@ dkim_collect_input = FALSE;
#ifdef SUPPORT_TLS
tls_in.certificate_verified = FALSE;
+# ifdef EXPERIMENTAL_DANE
+tls_in.dane_verified = FALSE;
+# endif
tls_in.cipher = NULL;
tls_in.ourcert = NULL;
tls_in.peercert = NULL;
@@ -492,7 +495,7 @@ for (;;)
if (Ustrncmp(p, "rozen", 5) == 0)
{
deliver_freeze = TRUE;
- sscanf(big_buffer+7, TIME_T_FMT, &deliver_frozen_at);
+ sscanf(CS big_buffer+7, TIME_T_FMT, &deliver_frozen_at);
}
break;
diff --git a/src/src/structs.h b/src/src/structs.h
index 80c23fb0a..4f7862dc5 100644
--- a/src/src/structs.h
+++ b/src/src/structs.h
@@ -495,6 +495,9 @@ typedef struct address_item_propagated {
# define af_prdr_used 0x08000000 /* delivery used SMTP PRDR */
#endif
#define af_force_command 0x10000000 /* force_command in pipe transport */
+#ifdef EXPERIMENTAL_DANE
+# define af_dane_verified 0x20000000 /* TLS cert verify done with DANE */
+#endif
/* These flags must be propagated when a child is created */
diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c
index c031b8e4d..2e95a467a 100644
--- a/src/src/tls-openssl.c
+++ b/src/src/tls-openssl.c
@@ -25,6 +25,10 @@ functions from the OpenSSL library. */
#ifndef DISABLE_OCSP
# include <openssl/ocsp.h>
#endif
+#ifdef EXPERIMENTAL_DANE
+# include <danessl.h>
+#endif
+
#ifndef DISABLE_OCSP
# define EXIM_OCSP_SKEW_SECONDS (300L)
@@ -423,6 +427,53 @@ return verify_callback(state, x509ctx, &tls_in, &server_verify_callback_called,
}
+#ifdef EXPERIMENTAL_DANE
+
+/* This gets called *by* the dane library verify callback, which interposes
+itself.
+*/
+static int
+verify_callback_client_dane(int state, X509_STORE_CTX * x509ctx)
+{
+X509 * cert = X509_STORE_CTX_get_current_cert(x509ctx);
+static uschar txt[256];
+#ifdef EXPERIMENTAL_TPDA
+int depth = X509_STORE_CTX_get_error_depth(x509ctx);
+#endif
+
+X509_NAME_oneline(X509_get_subject_name(cert), CS txt, sizeof(txt));
+
+DEBUG(D_tls) debug_printf("verify_callback_client_dane: %s\n", txt);
+tls_out.peerdn = txt;
+tls_out.peercert = X509_dup(cert);
+
+#ifdef EXPERIMENTAL_TPDA
+ if (client_static_cbinfo->event_action)
+ {
+ if (tpda_raise_event(client_static_cbinfo->event_action,
+ US"tls:cert", string_sprintf("%d", depth)) == DEFER)
+ {
+ log_write(0, LOG_MAIN, "DANE verify denied by event-action: "
+ "depth=%d cert=%s", depth, txt);
+ tls_out.certificate_verified = FALSE;
+ return 0; /* reject */
+ }
+ if (depth != 0)
+ {
+ X509_free(tls_out.peercert);
+ tls_out.peercert = NULL;
+ }
+ }
+#endif
+
+if (state == 1)
+ tls_out.dane_verified =
+ tls_out.certificate_verified = TRUE;
+return 1;
+}
+
+#endif /*EXPERIMENTAL_DANE*/
+
/*************************************************
* Information callback *
@@ -1039,7 +1090,6 @@ return i;
#endif /*!DISABLE_OCSP*/
-
/*************************************************
* Initialize for TLS *
*************************************************/
@@ -1048,13 +1098,14 @@ return i;
of the library. We allocate and return a context structure.
Arguments:
+ ctxp returned SSL context
host connected host, if client; NULL if server
dhparam DH parameter file
certificate certificate file
privatekey private key
ocsp_file file of stapling info (server); flag for require ocsp (client)
addr address if client; NULL if server (for some randomness)
- cbp place to put allocated context
+ cbp place to put allocated callback context
Returns: OK/DEFER/FAIL
*/
@@ -1463,6 +1514,9 @@ if (expciphers != NULL)
optional, set up appropriately. */
tls_in.certificate_verified = FALSE;
+#ifdef EXPERIMENTAL_DANE
+tls_in.dane_verified = FALSE;
+#endif
server_verify_callback_called = FALSE;
if (verify_check_host(&tls_verify_hosts) == OK)
@@ -1576,6 +1630,147 @@ return OK;
+static int
+tls_client_basic_ctx_init(SSL_CTX * ctx,
+ host_item * host, smtp_transport_options_block * ob
+#ifdef EXPERIMENTAL_CERTNAMES
+ , tls_ext_ctx_cb * cbinfo
+#endif
+ )
+{
+int rc;
+/* stick to the old behaviour for compatibility if tls_verify_certificates is
+ set but both tls_verify_hosts and tls_try_verify_hosts is not set. Check only
+ the specified host patterns if one of them is defined */
+
+if ((!ob->tls_verify_hosts && !ob->tls_try_verify_hosts) ||
+ (verify_check_host(&ob->tls_verify_hosts) == OK))
+ {
+ if ((rc = setup_certs(ctx, ob->tls_verify_certificates,
+ ob->tls_crl, host, FALSE, verify_callback_client)) != OK)
+ return rc;
+ client_verify_optional = FALSE;
+
+#ifdef EXPERIMENTAL_CERTNAMES
+ if (ob->tls_verify_cert_hostnames)
+ {
+ if (!expand_check(ob->tls_verify_cert_hostnames,
+ US"tls_verify_cert_hostnames",
+ &cbinfo->verify_cert_hostnames))
+ return FAIL;
+ if (cbinfo->verify_cert_hostnames)
+ DEBUG(D_tls) debug_printf("Cert hostname to check: \"%s\"\n",
+ cbinfo->verify_cert_hostnames);
+ }
+#endif
+ }
+else if (verify_check_host(&ob->tls_try_verify_hosts) == OK)
+ {
+ if ((rc = setup_certs(ctx, ob->tls_verify_certificates,
+ ob->tls_crl, host, TRUE, verify_callback_client)) != OK)
+ return rc;
+ client_verify_optional = TRUE;
+ }
+
+return OK;
+}
+
+
+#ifdef EXPERIMENTAL_DANE
+static int
+tlsa_lookup(host_item * host, dns_answer * dnsa,
+ BOOL dane_required, BOOL * dane)
+{
+/* move this out to host.c given the similarity to dns_lookup() ? */
+uschar buffer[300];
+uschar * fullname = buffer;
+
+/* TLSA lookup string */
+(void)sprintf(CS buffer, "_%d._tcp.%.256s", host->port, host->name);
+
+switch (dns_lookup(dnsa, buffer, T_TLSA, &fullname))
+ {
+ case DNS_AGAIN:
+ return DEFER; /* just defer this TLS'd conn */
+
+ default:
+ case DNS_FAIL:
+ if (dane_required)
+ {
+ log_write(0, LOG_MAIN, "DANE error: TLSA lookup failed");
+ return FAIL;
+ }
+ break;
+
+ case DNS_SUCCEED:
+ if (!dns_is_secure(dnsa))
+ {
+ log_write(0, LOG_MAIN, "DANE error: TLSA lookup not DNSSEC");
+ return DEFER;
+ }
+ *dane = TRUE;
+ break;
+ }
+return OK;
+}
+
+
+static int
+dane_tlsa_load(SSL * ssl, host_item * host, dns_answer * dnsa)
+{
+dns_record * rr;
+dns_scan dnss;
+const char * hostnames[2] = { CS host->name, NULL };
+int found = 0;
+
+if (DANESSL_init(ssl, NULL, hostnames) != 1)
+ return tls_error(US"hostnames load", host, NULL);
+
+for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
+ rr;
+ rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)
+ ) if (rr->type == T_TLSA)
+ {
+ uschar * p = rr->data;
+ uint8_t usage, selector, mtype;
+ const char * mdname;
+
+ found++;
+ usage = *p++;
+ selector = *p++;
+ mtype = *p++;
+
+ switch (mtype)
+ {
+ default:
+ log_write(0, LOG_MAIN,
+ "DANE error: TLSA record w/bad mtype 0x%x", mtype);
+ return FAIL;
+ case 0: mdname = NULL; break;
+ case 1: mdname = "sha256"; break;
+ case 2: mdname = "sha512"; break;
+ }
+
+ switch (DANESSL_add_tlsa(ssl, usage, selector, mdname, p, rr->size - 3))
+ {
+ default:
+ case 0: /* action not taken */
+ return tls_error(US"tlsa load", host, NULL);
+ case 1: break;
+ }
+
+ tls_out.tlsa_usage |= 1<<usage;
+ }
+
+if (found)
+ return OK;
+
+log_write(0, LOG_MAIN, "DANE error: No TLSA records");
+return FAIL;
+}
+#endif /*EXPERIMENTAL_DANE*/
+
+
/*************************************************
* Start a TLS session in a client *
@@ -1601,16 +1796,70 @@ tls_client_start(int fd, host_item *host, address_item *addr,
smtp_transport_options_block * ob =
(smtp_transport_options_block *)tb->options_block;
static uschar txt[256];
-uschar *expciphers;
-X509* server_cert;
+uschar * expciphers;
+X509 * server_cert;
int rc;
static uschar cipherbuf[256];
+
#ifndef DISABLE_OCSP
-BOOL require_ocsp = verify_check_this_host(&ob->hosts_require_ocsp,
- NULL, host->name, host->address, NULL) == OK;
-BOOL request_ocsp = require_ocsp ? TRUE
- : verify_check_this_host(&ob->hosts_request_ocsp,
- NULL, host->name, host->address, NULL) == OK;
+BOOL request_ocsp = FALSE;
+BOOL require_ocsp = FALSE;
+#endif
+#ifdef EXPERIMENTAL_DANE
+dns_answer tlsa_dnsa;
+BOOL dane = FALSE;
+BOOL dane_required;
+#endif
+
+#ifdef EXPERIMENTAL_DANE
+tls_out.dane_verified = FALSE;
+tls_out.tlsa_usage = 0;
+dane_required = verify_check_this_host(&ob->hosts_require_dane, NULL,
+ host->name, host->address, NULL) == OK;
+
+if (host->dnssec == DS_YES)
+ {
+ if( dane_required
+ || verify_check_this_host(&ob->hosts_try_dane, NULL,
+ host->name, host->address, NULL) == OK
+ )
+ if ((rc = tlsa_lookup(host, &tlsa_dnsa, dane_required, &dane)) != OK)
+ return rc;
+ }
+else if (dane_required)
+ {
+ /*XXX a shame we only find this after making tcp & smtp connection */
+ /* move the test earlier? */
+ log_write(0, LOG_MAIN, "DANE error: previous lookup not DNSSEC");
+ return FAIL;
+ }
+#endif
+
+#ifndef DISABLE_OCSP
+ {
+ if ((require_ocsp = verify_check_this_host(&ob->hosts_require_ocsp,
+ NULL, host->name, host->address, NULL) == OK))
+ request_ocsp = TRUE;
+ else
+ {
+# ifdef EXPERIMENTAL_DANE
+ if ( dane
+ && ob->hosts_request_ocsp[0] == '*'
+ && ob->hosts_request_ocsp[1] == '\0'
+ )
+ {
+ /* Unchanged from default. Use a safer one under DANE */
+ request_ocsp = TRUE;
+ ob->hosts_request_ocsp = US"${if or { {= {0}{$tls_out_tlsa_usage}} "
+ " {= {4}{$tls_out_tlsa_usage}} } "
+ " {*}{}}";
+ }
+ else
+# endif
+ request_ocsp = verify_check_this_host(&ob->hosts_request_ocsp,
+ NULL, host->name, host->address, NULL) == OK;
+ }
+ }
#endif
rc = tls_init(&client_ctx, host, NULL,
@@ -1641,38 +1890,26 @@ if (expciphers != NULL)
return tls_error(US"SSL_CTX_set_cipher_list", host, NULL);
}
-/* stick to the old behaviour for compatibility if tls_verify_certificates is
- set but both tls_verify_hosts and tls_try_verify_hosts is not set. Check only
- the specified host patterns if one of them is defined */
-
-if ((!ob->tls_verify_hosts && !ob->tls_try_verify_hosts) ||
- (verify_check_host(&ob->tls_verify_hosts) == OK))
+#ifdef EXPERIMENTAL_DANE
+if (dane)
{
- if ((rc = setup_certs(client_ctx, ob->tls_verify_certificates,
- ob->tls_crl, host, FALSE, verify_callback_client)) != OK)
- return rc;
- client_verify_optional = FALSE;
+ SSL_CTX_set_verify(client_ctx, SSL_VERIFY_PEER, verify_callback_client_dane);
+
+ if (!DANESSL_library_init())
+ return tls_error(US"library init", host, NULL);
+ if (DANESSL_CTX_init(client_ctx) <= 0)
+ return tls_error(US"context init", host, NULL);
+ }
+else
+
+#endif
+ if ((rc = tls_client_basic_ctx_init(client_ctx, host, ob
#ifdef EXPERIMENTAL_CERTNAMES
- if (ob->tls_verify_cert_hostnames)
- {
- if (!expand_check(ob->tls_verify_cert_hostnames,
- US"tls_verify_cert_hostnames",
- &client_static_cbinfo->verify_cert_hostnames))
- return FAIL;
- if (client_static_cbinfo->verify_cert_hostnames)
- DEBUG(D_tls) debug_printf("Cert hostname to check: \"%s\"\n",
- client_static_cbinfo->verify_cert_hostnames);
- }
+ , client_static_cbinfo
#endif
- }
-else if (verify_check_host(&ob->tls_try_verify_hosts) == OK)
- {
- if ((rc = setup_certs(client_ctx, ob->tls_verify_certificates,
- ob->tls_crl, host, TRUE, verify_callback_client)) != OK)
+ )) != OK)
return rc;
- client_verify_optional = TRUE;
- }
if ((client_ssl = SSL_new(client_ctx)) == NULL)
return tls_error(US"SSL_new", host, NULL);
@@ -1703,9 +1940,34 @@ if (ob->tls_sni)
}
}
+#ifdef EXPERIMENTAL_DANE
+if (dane)
+ if ((rc = dane_tlsa_load(client_ssl, host, &tlsa_dnsa)) != OK)
+ return rc;
+#endif
+
#ifndef DISABLE_OCSP
/* Request certificate status at connection-time. If the server
does OCSP stapling we will get the callback (set in tls_init()) */
+# ifdef EXPERIMENTAL_DANE
+if (request_ocsp)
+ {
+ const uschar * s;
+ if ( (s = ob->hosts_require_ocsp) && Ustrstr(s, US"tls_out_tlsa_usage")
+ || (s = ob->hosts_request_ocsp) && Ustrstr(s, US"tls_out_tlsa_usage")
+ )
+ { /* Re-eval now $tls_out_tlsa_usage is populated. If
+ this means we avoid the OCSP request, we wasted the setup
+ cost in tls_init(). */
+ require_ocsp = verify_check_this_host(&ob->hosts_require_ocsp,
+ NULL, host->name, host->address, NULL) == OK;
+ request_ocsp = require_ocsp ? TRUE
+ : verify_check_this_host(&ob->hosts_request_ocsp,
+ NULL, host->name, host->address, NULL) == OK;
+ }
+ }
+# endif
+
if (request_ocsp)
{
SSL_set_tlsext_status_type(client_ssl, TLSEXT_STATUSTYPE_ocsp);
@@ -1718,6 +1980,10 @@ if (request_ocsp)
client_static_cbinfo->event_action = tb->tpda_event_action;
#endif
+#ifdef EXPERIMENTAL_TPDA
+client_static_cbinfo->event_action = tb->tpda_event_action;
+#endif
+
/* There doesn't seem to be a built-in timeout on connection. */
DEBUG(D_tls) debug_printf("Calling SSL_connect\n");
@@ -1726,6 +1992,11 @@ alarm(ob->command_timeout);
rc = SSL_connect(client_ssl);
alarm(0);
+#ifdef EXPERIMENTAL_DANE
+if (dane)
+ DANESSL_cleanup(client_ssl);
+#endif
+
if (rc <= 0)
return tls_error(US"SSL_connect", host, sigalrm_seen ? US"timed out" : NULL);
diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c
index 0dfa01958..7b2a7d559 100644
--- a/src/src/transports/smtp.c
+++ b/src/src/transports/smtp.c
@@ -109,6 +109,10 @@ optionlist smtp_transport_options[] = {
{ "hosts_require_auth", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, hosts_require_auth) },
#ifdef SUPPORT_TLS
+# ifdef EXPERIMENTAL_DANE
+ { "hosts_require_dane", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, hosts_require_dane) },
+# endif
# ifndef DISABLE_OCSP
{ "hosts_require_ocsp", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, hosts_require_ocsp) },
@@ -118,6 +122,10 @@ optionlist smtp_transport_options[] = {
#endif
{ "hosts_try_auth", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, hosts_try_auth) },
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE)
+ { "hosts_try_dane", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, hosts_try_dane) },
+#endif
#ifndef DISABLE_PRDR
{ "hosts_try_prdr", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, hosts_try_prdr) },
@@ -196,11 +204,15 @@ smtp_transport_options_block smtp_transport_option_defaults = {
NULL, /* serialize_hosts */
NULL, /* hosts_try_auth */
NULL, /* hosts_require_auth */
+#ifdef EXPERIMENTAL_DANE
+ NULL, /* hosts_try_dane */
+ NULL, /* hosts_require_dane */
+#endif
#ifndef DISABLE_PRDR
NULL, /* hosts_try_prdr */
#endif
#ifndef DISABLE_OCSP
- US"*", /* hosts_request_ocsp */
+ US"*", /* hosts_request_ocsp (except under DANE) */
NULL, /* hosts_require_ocsp */
#endif
NULL, /* hosts_require_tls */
@@ -1576,8 +1588,13 @@ if (tls_out.active >= 0)
/* If the host is required to use a secure channel, ensure that we
have one. */
-else if (verify_check_this_host(&(ob->hosts_require_tls), NULL, host->name,
- host->address, NULL) == OK)
+else if ( verify_check_this_host(&(ob->hosts_require_tls), NULL, host->name,
+ host->address, NULL) == OK
+#ifdef EXPERIMENTAL_DANE
+ || verify_check_this_host(&(ob->hosts_require_dane), NULL, host->name,
+ host->address, NULL) == OK
+#endif
+ )
{
save_errno = ERRNO_TLSREQUIRED;
message = string_sprintf("a TLS session is required for %s [%s], but %s",
@@ -3277,10 +3294,16 @@ for (cutoff_retry = 0; expired &&
happens inside smtp_deliver().] */
#ifdef SUPPORT_TLS
- if (rc == DEFER && first_addr->basic_errno == ERRNO_TLSFAILURE &&
- ob->tls_tempfail_tryclear &&
- verify_check_this_host(&(ob->hosts_require_tls), NULL, host->name,
- host->address, NULL) != OK)
+ if ( rc == DEFER
+ && first_addr->basic_errno == ERRNO_TLSFAILURE
+ && ob->tls_tempfail_tryclear
+ && verify_check_this_host(&(ob->hosts_require_tls), NULL, host->name,
+ host->address, NULL) != OK
+# ifdef EXPERIMENTAL_DANE
+ && verify_check_this_host(&(ob->hosts_require_dane), NULL, host->name,
+ host->address, NULL) != OK
+# endif
+ )
{
log_write(0, LOG_MAIN, "TLS session failure: delivering unencrypted "
"to %s [%s] (not in hosts_require_tls)", host->name, host->address);
@@ -3294,7 +3317,7 @@ for (cutoff_retry = 0; expired &&
tpda_deferred(first_addr, host);
# endif
}
-#endif
+#endif /*SUPPORT_TLS*/
}
/* Delivery attempt finished */
diff --git a/src/src/transports/smtp.h b/src/src/transports/smtp.h
index 3030f3a91..4494adfb2 100644
--- a/src/src/transports/smtp.h
+++ b/src/src/transports/smtp.h
@@ -21,6 +21,10 @@ typedef struct {
uschar *serialize_hosts;
uschar *hosts_try_auth;
uschar *hosts_require_auth;
+#ifdef EXPERIMENTAL_DANE
+ uschar *hosts_try_dane;
+ uschar *hosts_require_dane;
+#endif
#ifndef DISABLE_PRDR
uschar *hosts_try_prdr;
#endif
diff --git a/src/src/verify.c b/src/src/verify.c
index 8564aacc2..d2ecb9cde 100644
--- a/src/src/verify.c
+++ b/src/src/verify.c
@@ -661,9 +661,16 @@ else
if the options permit it for this host. */
if (rc != OK)
{
- if (rc == DEFER && ob->tls_tempfail_tryclear && !smtps &&
- verify_check_this_host(&(ob->hosts_require_tls), NULL, host->name,
- host->address, NULL) != OK)
+ if ( rc == DEFER
+ && ob->tls_tempfail_tryclear
+ && !smtps
+ && verify_check_this_host(&(ob->hosts_require_tls), NULL,
+ host->name, host->address, NULL) != OK
+#ifdef EXPERIMENTAL_DANE
+ && verify_check_this_host(&(ob->hosts_require_dane), NULL,
+ host->name, host->address, NULL) != OK
+#endif
+ )
{
(void)close(inblock.sock);
#ifdef EXPERIMENTAL_TPDA
@@ -697,8 +704,13 @@ else
/* If the host is required to use a secure channel, ensure that we have one. */
if (tls_out.active < 0)
- if (verify_check_this_host(&(ob->hosts_require_tls), NULL, host->name,
- host->address, NULL) == OK)
+ if ( verify_check_this_host(&(ob->hosts_require_tls), NULL, host->name,
+ host->address, NULL) == OK
+#ifdef EXPERIMENTAL_DANE
+ || verify_check_this_host(&(ob->hosts_require_dane), NULL, host->name,
+ host->address, NULL) == OK
+#endif
+ )
{
/*save_errno = ERRNO_TLSREQUIRED;*/
log_write(0, LOG_MAIN, "a TLS session is required for %s [%s], but %s",