From e682570f275e60cf75f013c234a0561a451ab559 Mon Sep 17 00:00:00 2001 From: Todd Lyons Date: Wed, 5 Mar 2014 06:17:54 -0800 Subject: Framework to build dane support --- src/OS/Makefile-Base | 3 +- src/scripts/MakeLinks | 4 + src/src/config.h.defaults | 1 + src/src/dane-gnu.c | 21 + src/src/dane-openssl.c | 1306 +++++++++++++++++++++++++++++++++++++++++++++ src/src/dane.c | 46 ++ src/src/danessl.h | 31 ++ src/src/globals.c | 3 + src/src/globals.h | 3 + 9 files changed, 1417 insertions(+), 1 deletion(-) create mode 100644 src/src/dane-gnu.c create mode 100644 src/src/dane-openssl.c create mode 100644 src/src/dane.c create mode 100644 src/src/danessl.h (limited to 'src') diff --git a/src/OS/Makefile-Base b/src/OS/Makefile-Base index 87a803704..8937f3cad 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. @@ -541,6 +541,7 @@ acl.o: $(HDRS) acl.c child.o: $(HDRS) child.c crypt16.o: $(HDRS) crypt16.c daemon.o: $(HDRS) daemon.c +dane.o: $(HDRS) dane.c dane-gnu.c dane-openssl.c dbfn.o: $(HDRS) dbfn.c debug.o: $(HDRS) debug.c deliver.o: $(HDRS) deliver.c 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/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..790b4f079 --- /dev/null +++ b/src/src/dane-openssl.c @@ -0,0 +1,1306 @@ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#if OPENSSL_VERSION_NUMBER < 0x1000000fL +#error "OpenSSL 1.0.0 or higher required" +#else + +#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 + +#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; + unsigned char *buf2; + unsigned int len; + + /* + * 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 == NULL) { + 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) == 0) + 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. + */ + if (name) + return X509_set_issuer_name(cert, name); + return X509_set_issuer_name(cert, 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 == 0 && + (serverAuth = OBJ_nid2obj(NID_server_auth)) == 0) { + DANEerr(DANE_F_GROW_CHAIN, ERR_R_MALLOC_FAILURE); + return 0; + } + if (!*xs && (*xs = sk_X509_new_null()) == 0) { + 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 == 0 || newkey == 0 || (cert = X509_new()) == 0) + 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) { + if (!top && wrap_to_root) + ret = grow_chain(dane, UNTRUSTED, cert); + else + ret = grow_chain(dane, 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)) == 0) { + /* + * 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)) == 0) { + 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)) == 0 || + !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 == 0) { + if ((ctx->chain = sk_X509_new_null()) != 0 && + 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 = dane->hosts; + + for (/* NOP */; 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) +{ + register char *cp = name + len; + + while (len > 0 && *--cp == 0) + --len; /* Ignore trailing NULs */ + if (len <= 0) + return 0; + for (cp = name; *cp; cp++) { + register 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 == 0 || (i = X509_NAME_get_index_by_NID(name, nid, -1)) < 0) + return 0; + if ((entry = X509_NAME_get_entry(name, i)) == 0) + return 0; + if ((entry_str = X509_NAME_ENTRY_get_data(entry)) == 0) + 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; + int got_altname = 0; + 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 = 1; + certid = parse_dns_name(gn); + if (certid && *certid) { + if ((matched = match_name(certid, dane)) == 0) + continue; + if ((dane->mhost = OPENSSL_strdup(certid)) == 0) + 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 == 0) { + 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); + + 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? */ + + 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)) == 0 || cert == 0) + 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 == 0) { + DANEerr(DANE_F_LIST_ALLOC, ERR_R_MALLOC_FAILURE); + return 0; + } + if ((l = (dane_list) OPENSSL_malloc(sizeof(*l))) == 0) { + 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) list; + dane_list next; + + for (/* NOP */; 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); +} + +void DANESSL_cleanup(SSL *ssl) +{ + SSL_DANE *dane; + int u; + + if (dane_idx < 0 || (dane = SSL_get_ex_data(ssl, dane_idx)) == 0) + 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 = 0; + + while (*src) { + DANE_HOST_LIST elem = (DANE_HOST_LIST) OPENSSL_malloc(sizeof(*elem)); + if (elem == 0) { + list_free(head, OPENSSL_freeFunc); + return 0; + } + elem->value = OPENSSL_strdup(*src++); + LINSERT(head, elem); + } + return head; +} + +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; + + if (dane_idx < 0 || (dane = SSL_get_ex_data(ssl, dane_idx)) == 0) { + 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)) == 0) { + 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 == 0) { + 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) == 0) { \ + 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 == 0) { + 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)) == 0) + xkfreeret(0); + d->value->datalen = dlen; + memcpy(d->value->data, data, dlen); + if (!m) { + if ((m = (DANE_MTYPE_LIST) list_alloc(sizeof(*m->value))) == 0) { + 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))) == 0) { + 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; +} + +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 + 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))) == 0) { + 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->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)) == 0) { + DANEerr(DANE_F_SSL_DANE_INIT, ERR_R_MALLOC_FAILURE); + DANESSL_cleanup(ssl); + return 0; + } + + return 1; +} + +int DANESSL_CTX_init(SSL_CTX *ctx) +{ + 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); +} + +int DANESSL_library_init(void) +{ + 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 */ diff --git a/src/src/dane.c b/src/src/dane.c new file mode 100644 index 000000000..54fd00cee --- /dev/null +++ b/src/src/dane.c @@ -0,0 +1,46 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* See the file NOTICE for conditions of use and distribution. */ + +/* This module provides TLS (aka SSL) support for Exim. The code for OpenSSL is +based on a patch that was originally contributed by Steve Haslam. It was +adapted from stunnel, a GPL program by Michal Trojnara. The code for GNU TLS is +based on a patch contributed by Nikos Mavroyanopoulos. Because these packages +are so very different, the functions for each are kept in separate files. The +relevant file is #included as required, after any any common functions. + +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 +#include + +/*- + * 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/globals.c b/src/src/globals.c index d3f99877c..7d4ab63a1 100644 --- a/src/src/globals.c +++ b/src/src/globals.c @@ -633,6 +633,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; diff --git a/src/src/globals.h b/src/src/globals.h index 2bedcf523..32ddd16e2 100644 --- a/src/src/globals.h +++ b/src/src/globals.h @@ -385,6 +385,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 */ -- cgit v1.2.3 From 96e47838f9aaea7f89685163bcb01164f4444378 Mon Sep 17 00:00:00 2001 From: Todd Lyons Date: Mon, 24 Mar 2014 11:24:23 -0700 Subject: Create a hosts_try_dane transport option, does nothing yet --- src/src/transports/smtp.c | 7 +++++++ src/src/transports/smtp.h | 3 +++ 2 files changed, 10 insertions(+) (limited to 'src') diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index 40eebe8b6..2e3a6ced4 100644 --- a/src/src/transports/smtp.c +++ b/src/src/transports/smtp.c @@ -118,6 +118,10 @@ optionlist smtp_transport_options[] = { #endif { "hosts_try_auth", opt_stringptr, (void *)offsetof(smtp_transport_options_block, hosts_try_auth) }, +#ifdef 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) }, @@ -200,6 +204,9 @@ 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 */ +#endif #ifndef DISABLE_PRDR NULL, /* hosts_try_prdr */ #endif diff --git a/src/src/transports/smtp.h b/src/src/transports/smtp.h index dd41e1f15..018f9cfef 100644 --- a/src/src/transports/smtp.h +++ b/src/src/transports/smtp.h @@ -21,6 +21,9 @@ typedef struct { uschar *serialize_hosts; uschar *hosts_try_auth; uschar *hosts_require_auth; +#ifdef EXPERIMENTAL_DANE + uschar *hosts_try_dane; +#endif #ifndef DISABLE_PRDR uschar *hosts_try_prdr; #endif -- cgit v1.2.3 From 880a1e7750a0d8935d48788e35ff03e47f7f6ada Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Tue, 29 Jul 2014 22:27:42 +0100 Subject: Coding style closer to project norms --- src/src/dane-openssl.c | 2106 +++++++++++++++++++++++++----------------------- 1 file changed, 1102 insertions(+), 1004 deletions(-) (limited to 'src') diff --git a/src/src/dane-openssl.c b/src/src/dane-openssl.c index 790b4f079..781557075 100644 --- a/src/src/dane-openssl.c +++ b/src/src/dane-openssl.c @@ -12,8 +12,8 @@ #include #if OPENSSL_VERSION_NUMBER < 0x1000000fL -#error "OpenSSL 1.0.0 or higher required" -#else +# error "OpenSSL 1.0.0 or higher required" +#else /* remainder of file */ #include "danessl.h" @@ -46,8 +46,9 @@ #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[] = { +# 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"}, @@ -64,7 +65,8 @@ static ERR_STRING_DATA dane_str_functs[] = { {DANE_F_WRAP_CERT, "wrap_cert"}, {0, NULL} }; -static ERR_STRING_DATA dane_str_reasons[] = { +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"}, @@ -80,7 +82,7 @@ static ERR_STRING_DATA dane_str_reasons[] = { {DANE_R_NOSIGN_KEY, "Certificate usage 2 requires EC support"}, {0, NULL} }; -#endif +#endif /*OPENSSL_NO_ERR*/ #define DANEerr(f, r) ERR_PUT_error(err_lib_dane, (f), (r), __FILE__, __LINE__) @@ -96,1211 +98,1307 @@ static int wrap_to_root = 1; static void (*cert_free)(void *) = (void (*)(void *)) X509_free; static void (*pkey_free)(void *) = (void (*)(void *)) EVP_PKEY_free; -typedef struct dane_list { +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; +typedef struct dane_host_list +{ + struct dane_host_list *next; char *value; -} *DANE_HOST_LIST; +} *dane_host_list; -typedef struct dane_data { +typedef struct dane_data +{ size_t datalen; unsigned char data[0]; } *dane_data; -typedef struct DANE_DATA_LIST { - struct DANE_DATA_LIST *next; +typedef struct dane_data_list +{ + struct dane_data_list *next; dane_data value; -} *DANE_DATA_LIST; +} *dane_data_list; -typedef struct dane_mtype { +typedef struct dane_mtype +{ int mdlen; const EVP_MD *md; - DANE_DATA_LIST data; + dane_data_list data; } *dane_mtype; -typedef struct DANE_MTYPE_LIST { - struct DANE_MTYPE_LIST *next; +typedef struct dane_mtype_list +{ + struct dane_mtype_list *next; dane_mtype value; -} *DANE_MTYPE_LIST; +} *dane_mtype_list; -typedef struct dane_selector { +typedef struct dane_selector +{ uint8_t selector; - DANE_MTYPE_LIST mtype; + dane_mtype_list mtype; } *dane_selector; -typedef struct DANE_SELECTOR_LIST { - struct DANE_SELECTOR_LIST *next; +typedef struct dane_selector_list +{ + struct dane_selector_list *next; dane_selector value; -} *DANE_SELECTOR_LIST; +} *dane_selector_list; -typedef struct DANE_PKEY_LIST { - struct DANE_PKEY_LIST *next; +typedef struct dane_pkey_list +{ + struct dane_pkey_list *next; EVP_PKEY *value; -} *DANE_PKEY_LIST; +} *dane_pkey_list; -typedef struct DANE_CERT_LIST { - struct DANE_CERT_LIST *next; +typedef struct dane_cert_list +{ + struct dane_cert_list *next; X509 *value; -} *DANE_CERT_LIST; +} *dane_cert_list; -typedef struct SSL_DANE { +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]; + 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; +} ssl_dane; #ifndef X509_V_ERR_HOSTNAME_MISMATCH -#define X509_V_ERR_HOSTNAME_MISMATCH X509_V_ERR_APPLICATION_VERIFICATION +# define X509_V_ERR_HOSTNAME_MISMATCH X509_V_ERR_APPLICATION_VERIFICATION #endif -static int match(DANE_SELECTOR_LIST slist, X509 *cert, int depth) +static int +match(dane_selector_list slist, X509 *cert, int depth) { - int matched; +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. - */ +/* + * 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; + unsigned char *buf2; + unsigned int len; + + /* + * 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; + /* - * Loop over each selector, mtype, and associated data element looking - * for a match. + * If it is a digest, compute the corresponding digest of the + * DER data for comparison, otherwise, use the full object. */ - for (matched = 0; !matched && slist; slist = slist->next) { - DANE_MTYPE_LIST m; - unsigned char mdbuf[EVP_MAX_MD_SIZE]; - unsigned char *buf; - unsigned char *buf2; - unsigned int len; - - /* - * 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 == NULL) { - 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); + 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; } - return matched; -} + OPENSSL_free(buf); + } -static int push_ext(X509 *cert, X509_EXTENSION *ext) -{ - X509_EXTENSIONS *exts; - - if (ext) { - if ((exts = cert->cert_info->extensions) == 0) - 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; +return matched; } -static int add_ext(X509 *issuer, X509 *subject, int ext_nid, char *ext_val) +static int +push_ext(X509 *cert, X509_EXTENSION *ext) { - X509V3_CTX v3ctx; +X509_EXTENSIONS *exts; - X509V3_set_ctx(&v3ctx, issuer, subject, 0, 0, 0); - return push_ext(subject, X509V3_EXT_conf_nid(0, &v3ctx, ext_nid, ext_val)); +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 set_serial(X509 *cert, AUTHORITY_KEYID *akid, X509 *subject) +static int +add_ext(X509 *issuer, X509 *subject, int ext_nid, char *ext_val) { - int ret = 0; - BIGNUM *bn; - - if (akid && akid->serial) - return (X509_set_serialNumber(cert, akid->serial)); +X509V3_CTX v3ctx; - /* - * 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; +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 add_akid(X509 *cert, AUTHORITY_KEYID *akid) +static int +set_serial(X509 *cert, AUTHORITY_KEYID *akid, X509 *subject) { - int nid = NID_authority_key_identifier; - ASN1_STRING *id; - unsigned char c = 0; - int ret = 0; +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; +} - /* - * 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_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) +static int +add_skid(X509 *cert, AUTHORITY_KEYID *akid) { - int nid = NID_subject_key_identifier; +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; +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) +static X509_NAME * +akid_issuer_name(AUTHORITY_KEYID *akid) { - if (akid && akid->issuer) { - int i; - GENERAL_NAMES *gens = akid->issuer; +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); + 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); - } + if(gn->type == GEN_DIRNAME) + return (gn->d.dirn); } - return 0; + } +return 0; } -static int set_issuer_name(X509 *cert, AUTHORITY_KEYID *akid) +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. - */ - if (name) - return X509_set_issuer_name(cert, name); - return X509_set_issuer_name(cert, X509_get_subject_name(cert)); +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) +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; +STACK_OF(X509) **xs = trusted ? &dane->roots : &dane->chain; +static ASN1_OBJECT *serverAuth = 0; #define UNTRUSTED 0 #define TRUSTED 1 - if (trusted && serverAuth == 0 && - (serverAuth = OBJ_nid2obj(NID_server_auth)) == 0) { - DANEerr(DANE_F_GROW_CHAIN, ERR_R_MALLOC_FAILURE); - return 0; - } - if (!*xs && (*xs = sk_X509_new_null()) == 0) { - 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; - } +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; + } +return 1; } -static int wrap_issuer( - SSL_DANE *dane, - EVP_PKEY *key, - X509 *subject, - int depth, - int top -) +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); +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 == 0 || newkey == 0 || (cert = X509_new()) == 0) - 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) { - if (!top && wrap_to_root) - ret = grow_chain(dane, UNTRUSTED, cert); - else - ret = grow_chain(dane, TRUSTED, cert); - } - if (cert) - X509_free(cert); - return ret; +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) +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; +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) +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)) == 0) { - /* - * 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); - } +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(); - /* - * 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; +return done; } -static int set_trust_anchor(X509_STORE_CTX *ctx, SSL_DANE *dane, X509 *cert) +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; +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; } - - /* Make a shallow copy of the input untrusted chain. */ - if ((in = sk_X509_dup(in)) == 0) { - DANEerr(DANE_F_SET_TRUST_ANCHOR, ERR_R_MALLOC_FAILURE); - return -1; + else if(matched == MATCHED_CERT) + { + if(!wrap_cert(dane, ca, depth)) + matched = -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)) == 0 || - !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; + 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); +/* 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); +/* + * 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; +return matched; } -static int check_end_entity(X509_STORE_CTX *ctx, SSL_DANE *dane, X509 *cert) +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 == 0) { - if ((ctx->chain = sk_X509_new_null()) != 0 && - 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; +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) +static int +match_name(const char *certid, ssl_dane *dane) { - int multi = dane->multi; - DANE_HOST_LIST hosts = dane->hosts; - - for (/* NOP */; 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; +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; } - return 0; + + /* + * 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) +static char * +check_name(char *name, int len) { - register char *cp = name + len; - - while (len > 0 && *--cp == 0) - --len; /* Ignore trailing NULs */ - if (len <= 0) - return 0; - for (cp = name; *cp; cp++) { - register 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; +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) +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)); +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) +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 == 0 || (i = X509_NAME_get_index_by_NID(name, nid, -1)) < 0) - return 0; - if ((entry = X509_NAME_get_entry(name, i)) == 0) - return 0; - if ((entry_str = X509_NAME_ENTRY_get_data(entry)) == 0) - 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; +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) +static int +name_check(ssl_dane *dane, X509 *cert) { - int matched = 0; - int got_altname = 0; - 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 = 1; - certid = parse_dns_name(gn); - if (certid && *certid) { - if ((matched = match_name(certid, dane)) == 0) - continue; - if ((dane->mhost = OPENSSL_strdup(certid)) == 0) - 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 == 0) { - char *certid = parse_subject_name(cert); - if (certid != 0 && *certid && (matched = match_name(certid, dane)) != 0) - dane->mhost = certid; /* Already a copy */ +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; + } } - return matched; + 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) +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); - - 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; +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); + +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) { - 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; + if(matched < 0) + { + X509_STORE_CTX_set_error(ctx, X509_V_ERR_OUT_OF_MEM); + 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; - } + 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); +return ctx->verify(ctx); } -static int verify_cert(X509_STORE_CTX *ctx, void *unused_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? */ - - 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; +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? */ + +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); } - - ssl = X509_STORE_CTX_get_ex_data(ctx, ssl_idx); - if ((dane = SSL_get_ex_data(ssl, dane_idx)) == 0 || cert == 0) - 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(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); - } +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) + { /* - * Name checks and usage 0/1 constraint enforcement are delayed until - * X509_verify_cert() builds the full chain and calls our verify_chain() - * wrapper. + * Check that setting the untrusted chain updates the expected + * structure member at the expected offset. */ - dane->verify = ctx->verify; - ctx->verify = verify_chain; + X509_STORE_CTX_trusted_stack(ctx, dane->roots); + X509_STORE_CTX_set_chain(ctx, dane->chain); + OPENSSL_assert(ctx->untrusted == dane->chain); + } + } - return X509_verify_cert(ctx); +/* + * 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) +static dane_list +list_alloc(size_t vsize) { - void *value = (void *) OPENSSL_malloc(vsize); - dane_list l; - - if (value == 0) { - DANEerr(DANE_F_LIST_ALLOC, ERR_R_MALLOC_FAILURE); - return 0; - } - if ((l = (dane_list) OPENSSL_malloc(sizeof(*l))) == 0) { - OPENSSL_free(value); - DANEerr(DANE_F_LIST_ALLOC, ERR_R_MALLOC_FAILURE); - return 0; - } - l->next = 0; - l->value = value; - return l; +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 *)) +static void +list_free(void *list, void (*f)(void *)) { - dane_list head = (dane_list) list; - dane_list next; - - for (/* NOP */; head; head = next) { - next = head->next; - if (f && head->value) - f(head->value); - OPENSSL_free(head); - } +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) +static void +dane_mtype_free(void *p) { - list_free(((dane_mtype) p)->data, OPENSSL_freeFunc); - OPENSSL_free(p); +list_free(((dane_mtype) p)->data, OPENSSL_freeFunc); +OPENSSL_free(p); } -static void dane_selector_free(void *p) +static void +dane_selector_free(void *p) { - list_free(((dane_selector) p)->mtype, dane_mtype_free); - OPENSSL_free(p); +list_free(((dane_selector) p)->mtype, dane_mtype_free); +OPENSSL_free(p); } -void DANESSL_cleanup(SSL *ssl) +void +DANESSL_cleanup(SSL *ssl) { - SSL_DANE *dane; - int u; - - if (dane_idx < 0 || (dane = SSL_get_ex_data(ssl, dane_idx)) == 0) - 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); +ssl_dane *dane; +int u; + +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) +static dane_host_list +host_list_init(const char **src) { - DANE_HOST_LIST head = 0; - - while (*src) { - DANE_HOST_LIST elem = (DANE_HOST_LIST) OPENSSL_malloc(sizeof(*elem)); - if (elem == 0) { - list_free(head, OPENSSL_freeFunc); - return 0; - } - elem->value = OPENSSL_strdup(*src++); - LINSERT(head, elem); +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; } - return head; + elem->value = OPENSSL_strdup(*src++); + LINSERT(head, elem); + } +return head; } -int DANESSL_add_tlsa( - SSL *ssl, - uint8_t usage, - uint8_t selector, - const char *mdname, - unsigned const char *data, - size_t dlen -) +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; - - if (dane_idx < 0 || (dane = SSL_get_ex_data(ssl, dane_idx)) == 0) { - 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)) == 0) { - 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 == 0) { - X509 *x = 0; - EVP_PKEY *k = 0; - const unsigned char *p = data; +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; + +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) == 0) { \ - DANEerr(DANE_F_SSL_DANE_ADD_TLSA, ERR_R_MALLOC_FAILURE); \ - freeFunc((var)); \ - return 0; \ - } \ - (lvar)->next = 0; \ - lvar->value = var; \ - } while (0) + (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 == 0) { - 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; - } + 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)) == 0) - xkfreeret(0); - d->value->datalen = dlen; - memcpy(d->value->data, data, dlen); - if (!m) { - if ((m = (DANE_MTYPE_LIST) list_alloc(sizeof(*m->value))) == 0) { - 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))) == 0) { - 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); + } + +/* 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); } - LINSERT(m->value->data, d); - - if (xlist) - LINSERT(dane->certs, xlist); - else if (klist) - LINSERT(dane->pkeys, klist); - ++dane->count; - return 1; + 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; } -int DANESSL_init(SSL *ssl, const char *sni_domain, const char **hostnames) +int +DANESSL_init(SSL *ssl, const char *sni_domain, const char **hostnames) { - SSL_DANE *dane; - int i; +ssl_dane *dane; +int i; #ifdef OPENSSL_INTERNAL - SSL_CTX *sctx = SSL_get_SSL_CTX(ssl); +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; - } +if(sctx->app_verify_callback != verify_cert) + { + DANEerr(DANE_F_SSL_DANE_INIT, DANE_R_SCTX_INIT); + return -1; + } #else - if (dane_idx < 0) { - DANEerr(DANE_F_SSL_DANE_INIT, DANE_R_LIBRARY_INIT); - return -1; - } +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))) == 0) { - 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->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)) == 0) { - DANEerr(DANE_F_SSL_DANE_INIT, ERR_R_MALLOC_FAILURE); - DANESSL_cleanup(ssl); - return 0; - } +if(sni_domain && !SSL_set_tlsext_host_name(ssl, sni_domain)) + return 0; - return 1; +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->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; } -int DANESSL_CTX_init(SSL_CTX *ctx) +int +DANESSL_CTX_init(SSL_CTX *ctx) { - 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; +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) -) +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(); - } +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; + } +if (wlock) + CRYPTO_w_unlock(CRYPTO_LOCK_SSL_CTX); +else + CRYPTO_r_unlock(CRYPTO_LOCK_SSL_CTX); +return *value; } -static void dane_init(void) +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. - */ +/* + * 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); +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. - */ +/* + * 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()); +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()); +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); +/* + * 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); } -int DANESSL_library_init(void) +int +DANESSL_library_init(void) { - if (err_lib_dane < 0) - init_once(&err_lib_dane, ERR_get_next_error_library, dane_init); +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; +/* 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; + +DANEerr(DANE_F_SSL_DANE_LIBRARY_INIT, DANE_R_DANE_SUPPORT); +return 0; } #endif /* OPENSSL_VERSION_NUMBER */ +/* vi: aw ai sw=2 +*/ -- cgit v1.2.3 From 946ecbe0c046adc421dd897b34ed5b68229bba22 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Thu, 31 Jul 2014 21:52:48 +0100 Subject: Add interface documentation for the DANE library --- src/src/dane-openssl.c | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/src/tls-openssl.c | 3 +- 2 files changed, 93 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/src/dane-openssl.c b/src/src/dane-openssl.c index 781557075..c8099f645 100644 --- a/src/src/dane-openssl.c +++ b/src/src/dane-openssl.c @@ -1058,6 +1058,22 @@ 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) { @@ -1105,6 +1121,28 @@ while(*src) 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) @@ -1254,6 +1292,30 @@ else if(klist) 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 - but what names? + +Return + -1 on fatal error + 0 nonfatal error + 1 success +*/ + int DANESSL_init(SSL *ssl, const char *sni_domain, const char **hostnames) { @@ -1312,6 +1374,25 @@ if(hostnames && !(dane->hosts = host_list_init(hostnames))) 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) { @@ -1383,6 +1464,15 @@ if(!EVP_get_digestbyname(LN_sha512)) EVP_add_digest(EVP_sha512()); 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. + +*/ + int DANESSL_library_init(void) { @@ -1399,6 +1489,7 @@ 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/tls-openssl.c b/src/src/tls-openssl.c index 18994eaa9..eeff64f81 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -1008,13 +1008,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 */ -- cgit v1.2.3 From 043b12481513cec52c31717c8ad5248d2b344ad2 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Fri, 1 Aug 2014 18:16:53 +0100 Subject: Basic DANE entry points --- doc/doc-txt/experimental-spec.txt | 13 ++++ src/src/EDITME | 3 + src/src/dane-openssl.c | 5 ++ src/src/dane.c | 31 +++++---- src/src/exim.c | 3 + src/src/tls-openssl.c | 133 +++++++++++++++++++++++++++++--------- 6 files changed, 140 insertions(+), 48 deletions(-) (limited to 'src') diff --git a/doc/doc-txt/experimental-spec.txt b/doc/doc-txt/experimental-spec.txt index 6657f63c7..1a786356e 100644 --- a/doc/doc-txt/experimental-spec.txt +++ b/doc/doc-txt/experimental-spec.txt @@ -1130,6 +1130,7 @@ in a router. Exim will then send the success DSN himself if requested as if the next hop does not support DSN. Adding it to a redirect router makes no difference. + Certificate name checking -------------------------------------------------------------- The X509 certificates used for TLS are supposed be verified @@ -1148,6 +1149,18 @@ a single wildcard being the initial component of a 3-or-more component FQDN). +DANE +------------------------------------------------------------ +If dane is in use the following transport options are ignored: + tls_verify_hosts + tls_try_verify_hosts + tls_verify_certificates + tls_crl + tls_verify_cert_hostnames + hosts_require_ocsp + hosts_request_ocsp + + -------------------------------------------------------------- End of file 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/dane-openssl.c b/src/src/dane-openssl.c index c8099f645..407e6800d 100644 --- a/src/src/dane-openssl.c +++ b/src/src/dane-openssl.c @@ -1471,6 +1471,11 @@ 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 diff --git a/src/src/dane.c b/src/src/dane.c index 54fd00cee..20dfe5b18 100644 --- a/src/src/dane.c +++ b/src/src/dane.c @@ -2,15 +2,14 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* Copyright (c) University of Cambridge 1995 - 2012, 2014 */ /* See the file NOTICE for conditions of use and distribution. */ -/* This module provides TLS (aka SSL) support for Exim. The code for OpenSSL is -based on a patch that was originally contributed by Steve Haslam. It was -adapted from stunnel, a GPL program by Michal Trojnara. The code for GNU TLS is -based on a patch contributed by Nikos Mavroyanopoulos. Because these packages -are so very different, the functions for each are kept in separate files. The -relevant file is #included as required, after any any common functions. +/* 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. */ @@ -30,15 +29,15 @@ 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 +# 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 */ diff --git a/src/src/exim.c b/src/src/exim.c index 517b5435e..8a9de72ac 100644 --- a/src/src/exim.c +++ b/src/src/exim.c @@ -820,6 +820,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/tls-openssl.c b/src/src/tls-openssl.c index eeff64f81..b96dbbf04 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -1534,6 +1534,50 @@ 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; +} /************************************************* * Start a TLS session in a client * @@ -1562,12 +1606,30 @@ uschar *expciphers; X509* server_cert; int rc; static uschar cipherbuf[256]; + +#ifndef DISABLE_OCSP +BOOL require_ocsp = FALSE; +BOOL request_ocsp = FALSE; +#endif +#ifdef EXPERIMENTAL_DANE +BOOL dane_in_use; +#endif + +#ifdef EXPERIMENTAL_DANE +/*XXX TBD: test for transport options, and for TLSA records */ +dane_in_use = FALSE; + +if (!dane_in_use) +#endif + #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; + { + 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 rc = tls_init(&client_ctx, host, NULL, @@ -1598,38 +1660,24 @@ 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_in_use) { - 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; + if (!DANESSL_library_init()) + return tls_error(US"library init", host, US"DANE library error"); + if (DANESSL_CTX_init(client_ctx) <= 0) + return tls_error(US"context init", host, US"DANE library error"); + } +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); @@ -1671,6 +1719,23 @@ if (request_ocsp) } #endif +#ifdef EXPERIMENTAL_DANE +if (dane_in_use) + { + if (DANESSL_init(client_ssl, NULL, NULL /*??? hostnames*/) != 1) + return tls_error(US"hostnames load", host, US"DANE library error"); + + /* + foreach TLSA record + + DANESSL_add_tlsa(client_ssl, uint8_t usage, uint8_t selector, + const char *mdname, + unsigned const char *data, size_t dlen) + */ + } +#endif + + /* There doesn't seem to be a built-in timeout on connection. */ DEBUG(D_tls) debug_printf("Calling SSL_connect\n"); @@ -1679,6 +1744,10 @@ alarm(ob->command_timeout); rc = SSL_connect(client_ssl); alarm(0); +#ifdef EXPERIMENTAL_DANE +DANESSL_cleanup(client_ssl); /*XXX earliest possible callpoint. Too early? */ +#endif + if (rc <= 0) return tls_error(US"SSL_connect", host, sigalrm_seen ? US"timed out" : NULL); -- cgit v1.2.3 From 6634ac8dc1c8fa3f429835a4735adfeb1bcc4390 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Fri, 1 Aug 2014 19:07:56 +0100 Subject: Fix development-testing induced crash on second use --- src/src/dane-openssl.c | 17 +++++++++++++++++ src/src/tls-openssl.c | 6 +++--- test/scripts/2100-OpenSSL/2100 | 2 +- 3 files changed, 21 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/src/dane-openssl.c b/src/src/dane-openssl.c index 407e6800d..4a177807a 100644 --- a/src/src/dane-openssl.c +++ b/src/src/dane-openssl.c @@ -859,6 +859,8 @@ X509 *cert = ctx->cert; /* XXX: accessor? */ int matched = 0; int chain_length = sk_X509_num(ctx->chain); +DEBUG(D_tls) debug_printf("Dane library verify_chain fn called\n"); + issuer_rrs = dane->selectors[SSL_DANE_USAGE_LIMIT_ISSUER]; leaf_rrs = dane->selectors[SSL_DANE_USAGE_LIMIT_LEAF]; ctx->verify = dane->verify; @@ -950,6 +952,8 @@ int (*cb)(int, X509_STORE_CTX *) = ctx->verify_cb; int matched; X509 *cert = ctx->cert; /* XXX: accessor? */ +DEBUG(D_tls) debug_printf("Dane library verify_cert fn called\n"); + if(ssl_idx < 0) ssl_idx = SSL_get_ex_data_X509_STORE_CTX_idx(); if(dane_idx < 0) @@ -1080,6 +1084,8 @@ DANESSL_cleanup(SSL *ssl) ssl_dane *dane; int u; +DEBUG(D_tls) debug_printf("Dane library cleanup fn called\n"); + if(dane_idx < 0 || !(dane = SSL_get_ex_data(ssl, dane_idx))) return; (void) SSL_set_ex_data(ssl, dane_idx, 0); @@ -1100,6 +1106,7 @@ if(dane->roots) if(dane->chain) sk_X509_pop_free(dane->chain, X509_free); OPENSSL_free(dane); +DEBUG(D_tls) debug_printf("Dane library cleanup fn return\n"); } static dane_host_list @@ -1155,6 +1162,8 @@ dane_cert_list xlist = 0; dane_pkey_list klist = 0; const EVP_MD *md = 0; +DEBUG(D_tls) debug_printf("Dane add_tlsa\n"); + if(dane_idx < 0 || !(dane = SSL_get_ex_data(ssl, dane_idx))) { DANEerr(DANE_F_SSL_DANE_ADD_TLSA, DANE_R_DANE_INIT); @@ -1324,12 +1333,14 @@ 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); @@ -1351,7 +1362,11 @@ if(!SSL_set_ex_data(ssl, dane_idx, dane)) OPENSSL_free(dane); return 0; } +DEBUG(D_tls) debug_printf("Dane ssl-init: new dane struct: %p\n", dane); +dane->verify = 0; +dane->hosts = 0; +dane->thost = 0; dane->pkeys = 0; dane->certs = 0; dane->chain = 0; @@ -1396,6 +1411,7 @@ Return 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); @@ -1481,6 +1497,7 @@ Return 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); diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index b96dbbf04..fa29f4ed9 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -1608,16 +1608,16 @@ int rc; static uschar cipherbuf[256]; #ifndef DISABLE_OCSP -BOOL require_ocsp = FALSE; BOOL request_ocsp = FALSE; +BOOL require_ocsp = FALSE; #endif #ifdef EXPERIMENTAL_DANE -BOOL dane_in_use; +BOOL dane_in_use = FALSE; #endif #ifdef EXPERIMENTAL_DANE /*XXX TBD: test for transport options, and for TLSA records */ -dane_in_use = FALSE; +/*dane_in_use = TRUE;*/ if (!dane_in_use) #endif diff --git a/test/scripts/2100-OpenSSL/2100 b/test/scripts/2100-OpenSSL/2100 index c2b0f8981..61c2fd6fb 100644 --- a/test/scripts/2100-OpenSSL/2100 +++ b/test/scripts/2100-OpenSSL/2100 @@ -4,7 +4,7 @@ exim -DSERVER=server -bd -oX PORT_D exim CALLER@test.ex Testing **** -exim -qf +exim -d+all -qf **** killdaemon no_msglog_check -- cgit v1.2.3 From 868f567254760f9414689debe3639bc190dcc97e Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Thu, 7 Aug 2014 22:18:41 +0100 Subject: Sketch in library interface --- src/src/dane-openssl.c | 2 +- src/src/tls-openssl.c | 114 ++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 100 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/src/src/dane-openssl.c b/src/src/dane-openssl.c index 4a177807a..aee6323bf 100644 --- a/src/src/dane-openssl.c +++ b/src/src/dane-openssl.c @@ -1317,7 +1317,7 @@ call this with NULL. Arguments: ssl Connection handle sni_domain Optional peer server name - hostnames ?? list of names - but what names? + hostnames list of names to chack against peer cert Return -1 on fatal error diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index fa29f4ed9..8a4e5a7ae 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -1602,8 +1602,8 @@ tls_client_start(int fd, host_item *host, address_item *addr, { smtp_transport_options_block * ob = v_ob; static uschar txt[256]; -uschar *expciphers; -X509* server_cert; +uschar * expciphers; +X509 * server_cert; int rc; static uschar cipherbuf[256]; @@ -1612,14 +1612,70 @@ BOOL request_ocsp = FALSE; BOOL require_ocsp = FALSE; #endif #ifdef EXPERIMENTAL_DANE -BOOL dane_in_use = FALSE; +dns_answer tlsa_dnsa; +BOOL dane = FALSE; +BOOL dane_required; #endif #ifdef EXPERIMENTAL_DANE /*XXX TBD: test for transport options, and for TLSA records */ -/*dane_in_use = TRUE;*/ +/*dane = TRUE;*/ + +# ifdef notyet +dane_required = verify_check_this_host(&ob->hosts_require_dane, NULL, + host->name, host->address, NULL) == OK; +# else +dane_required = FALSE; +#endif + +if (host->dnssec == DS_YES) + { + if( dane_required + || verify_check_this_host(&ob->hosts_try_dane, NULL, + host->name, host->address, NULL) == OK + ) + { + /* move this out to host.c given the similarity to dns_lookup() ? */ + uschar buffer[300]; + int prefix_length; /* why do we want this? */ + uschar * fullname = buffer; + + /* TLSA lookup string */ + (void)sprintf(CS buffer, "_%d._tcp.%n%.256s", host->port, &prefix_length, + host->name); + + switch (rc = dns_lookup(&tlsa_dnsa, buffer, T_TLSA, &fullname)) + { + case DNS_AGAIN: + return DEFER; /* just defer this TLS'd conn */ + + default: + case DNS_FAIL: + if (dane_required) + { + /* log that TLSA lookup failed */ + return FAIL; + } + break; + + case DNS_SUCCEED: + if (!dns_is_secure(&tlsa_dnsa)) + { + /*log it - tlsa should never be non-dnssec */ + return DEFER; + } + dane = TRUE; + break; + } + } + } +else if (dane_required && !dane) + { + /* log that dnssec pre-req failed. Hmm - what? */ + return FAIL; + } -if (!dane_in_use) +if (!dane) /*XXX todo: enable ocsp with dane */ #endif #ifndef DISABLE_OCSP @@ -1661,7 +1717,7 @@ if (expciphers != NULL) } #ifdef EXPERIMENTAL_DANE -if (dane_in_use) +if (dane) { if (!DANESSL_library_init()) return tls_error(US"library init", host, US"DANE library error"); @@ -1720,18 +1776,46 @@ if (request_ocsp) #endif #ifdef EXPERIMENTAL_DANE -if (dane_in_use) +if (dane) { - if (DANESSL_init(client_ssl, NULL, NULL /*??? hostnames*/) != 1) + dns_record * rr; + dns_scan dnss; + uschar * hostnames[2] = { host->name, NULL }; + + if (DANESSL_init(client_ssl, NULL, hostnames) != 1) return tls_error(US"hostnames load", host, US"DANE library error"); - /* - foreach TLSA record - - DANESSL_add_tlsa(client_ssl, uint8_t usage, uint8_t selector, - const char *mdname, - unsigned const char *data, size_t dlen) - */ + for (rr = dns_next_rr(&tlsa_dnsa, &dnss, RESET_ANSWERS); + rr; + rr = dns_next_rr(&tlsa_dnsa, &dnss, RESET_NEXT) + ) if (rr->type == T_TLSA) + { + uschar * p = rr->data; + int usage, selector, mtype; + const char * mdname; + + GETSHORT(usage, p); + GETSHORT(selector, p); + GETSHORT(mtype, p); + + switch (mtype) + { + default: /* log bad */ return FAIL; + case 0: mdname = NULL; break; + case 1: mdname = "SHA2-256"; break; + case 2: mdname = "SHA2-512"; break; + } + + switch (DANESSL_add_tlsa(client_ssl, + (uint8_t) usage, (uint8_t) selector, + mdname, p, rr->size - (p - rr->data))) + { + default: + case 0: /* action not taken; log error */ + return FAIL; + case 1: break; + } + } } #endif -- cgit v1.2.3 From 12ee8cf9db5b6c81b0e492c64f305d7ba1cf4dc2 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Fri, 8 Aug 2014 20:45:24 +0100 Subject: Testsuite basics. Not actually excercising DANE yet, this will take additions in the fakedns and probably changes in certificates. --- src/src/tls-openssl.c | 3 +- test/confs/5800 | 74 +++++++++++++++++++++++++++++++++ test/confs/5850 | 72 ++++++++++++++++++++++++++++++++ test/log/5850 | 13 ++++++ test/scripts/2100-OpenSSL/2100 | 2 +- test/scripts/5800-DANE-GnuTLS/5800 | 14 +++++++ test/scripts/5800-DANE-GnuTLS/REQUIRES | 3 ++ test/scripts/5850-DANE-OpenSSL/5850 | 12 ++++++ test/scripts/5850-DANE-OpenSSL/REQUIRES | 3 ++ 9 files changed, 194 insertions(+), 2 deletions(-) create mode 100644 test/confs/5800 create mode 100644 test/confs/5850 create mode 100644 test/log/5850 create mode 100644 test/scripts/5800-DANE-GnuTLS/5800 create mode 100644 test/scripts/5800-DANE-GnuTLS/REQUIRES create mode 100644 test/scripts/5850-DANE-OpenSSL/5850 create mode 100644 test/scripts/5850-DANE-OpenSSL/REQUIRES (limited to 'src') diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index 8a4e5a7ae..201636db0 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -1829,7 +1829,8 @@ rc = SSL_connect(client_ssl); alarm(0); #ifdef EXPERIMENTAL_DANE -DANESSL_cleanup(client_ssl); /*XXX earliest possible callpoint. Too early? */ +if (dane) + DANESSL_cleanup(client_ssl); /*XXX earliest possible callpoint. Too early? */ #endif if (rc <= 0) diff --git a/test/confs/5800 b/test/confs/5800 new file mode 100644 index 000000000..f1bd09d1c --- /dev/null +++ b/test/confs/5800 @@ -0,0 +1,74 @@ +# Exim test configuration 5800 +# DANE + +SERVER= + +exim_path = EXIM_PATH +host_lookup_order = bydns +primary_hostname = myhost.test.ex +rfc1413_query_timeout = 0s +spool_directory = DIR/spool +log_file_path = DIR/spool/log/SERVER%slog +gecos_pattern = "" +gecos_name = CALLER_NAME + +# ----- Main settings ----- + +acl_smtp_rcpt = accept + +log_selector = +tls_peerdn + +queue_only +queue_run_in_order + +tls_advertise_hosts = * +# needed to force generation +tls_dhparam = historic + +# Set certificate only if server + +tls_certificate = ${if eq {SERVER}{server}{DIR/aux-fixed/cert1}fail} +tls_privatekey = ${if eq {SERVER}{server}{DIR/aux-fixed/cert1}fail} + +#tls_verify_hosts = * +#tls_verify_certificates = ${if eq {SERVER}{server}{DIR/aux-fixed/cert2}fail} + + +# ----- Routers ----- + +begin routers + +client: + driver = accept + condition = ${if eq {SERVER}{server}{no}{yes}} + retry_use_local_part + transport = send_to_server + +server: + driver = redirect + data = :blackhole: + + +# ----- Transports ----- + +begin transports + +send_to_server: + driver = smtp + allow_localhost + hosts = 127.0.0.1 + port = PORT_D +# tls_certificate = DIR/aux-fixed/cert2 +# tls_privatekey = DIR/aux-fixed/cert2 +# tls_verify_certificates = DIR/aux-fixed/cert2 + + +# ----- Retry ----- + + +begin retry + +* * F,5d,10s + + +# End diff --git a/test/confs/5850 b/test/confs/5850 new file mode 100644 index 000000000..ac967fcb8 --- /dev/null +++ b/test/confs/5850 @@ -0,0 +1,72 @@ +# Exim test configuration 5850 +# DANE + +SERVER= + +exim_path = EXIM_PATH +host_lookup_order = bydns +primary_hostname = myhost.test.ex +rfc1413_query_timeout = 0s +spool_directory = DIR/spool +log_file_path = DIR/spool/log/SERVER%slog +gecos_pattern = "" +gecos_name = CALLER_NAME + +# ----- Main settings ----- + +acl_smtp_rcpt = accept + +log_selector = +tls_peerdn + +queue_only +queue_run_in_order + +tls_advertise_hosts = * + +# Set certificate only if server + +tls_certificate = ${if eq {SERVER}{server}{DIR/aux-fixed/cert1}fail} +tls_privatekey = ${if eq {SERVER}{server}{DIR/aux-fixed/cert1}fail} + +#tls_verify_hosts = * +#tls_verify_certificates = ${if eq {SERVER}{server}{DIR/aux-fixed/cert2}fail} + + +# ----- Routers ----- + +begin routers + +client: + driver = accept + condition = ${if eq {SERVER}{server}{no}{yes}} + retry_use_local_part + transport = send_to_server + +server: + driver = redirect + data = :blackhole: + + +# ----- Transports ----- + +begin transports + +send_to_server: + driver = smtp + allow_localhost + hosts = 127.0.0.1 + port = PORT_D +# tls_certificate = DIR/aux-fixed/cert2 +# tls_privatekey = DIR/aux-fixed/cert2 +# tls_verify_certificates = DIR/aux-fixed/cert2 + + +# ----- Retry ----- + + +begin retry + +* * F,5d,10s + + +# End diff --git a/test/log/5850 b/test/log/5850 new file mode 100644 index 000000000..2913e7c4a --- /dev/null +++ b/test/log/5850 @@ -0,0 +1,13 @@ +1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss +1999-03-02 09:44:33 Start queue run: pid=pppp -qf +1999-03-02 09:44:33 10HmaX-0005vi-00 => CALLER@test.ex R=client T=send_to_server H=127.0.0.1 [127.0.0.1] X=TLSv1:AES256-SHA:256 DN="/C=UK/O=The Exim Maintainers/OU=Test Suite/CN=Phil Pennock" C="250 OK id=10HmaY-0005vi-00" +1999-03-02 09:44:33 10HmaX-0005vi-00 Completed +1999-03-02 09:44:33 End queue run: pid=pppp -qf + +******** SERVER ******** +1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225 +1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@myhost.test.ex H=localhost (myhost.test.ex) [127.0.0.1] P=esmtps X=TLSv1:AES256-SHA:256 S=sss id=E10HmaX-0005vi-00@myhost.test.ex +1999-03-02 09:44:33 Start queue run: pid=pppp -qf +1999-03-02 09:44:33 10HmaY-0005vi-00 => :blackhole: R=server +1999-03-02 09:44:33 10HmaY-0005vi-00 Completed +1999-03-02 09:44:33 End queue run: pid=pppp -qf diff --git a/test/scripts/2100-OpenSSL/2100 b/test/scripts/2100-OpenSSL/2100 index 61c2fd6fb..c2b0f8981 100644 --- a/test/scripts/2100-OpenSSL/2100 +++ b/test/scripts/2100-OpenSSL/2100 @@ -4,7 +4,7 @@ exim -DSERVER=server -bd -oX PORT_D exim CALLER@test.ex Testing **** -exim -d+all -qf +exim -qf **** killdaemon no_msglog_check diff --git a/test/scripts/5800-DANE-GnuTLS/5800 b/test/scripts/5800-DANE-GnuTLS/5800 new file mode 100644 index 000000000..07ad7406d --- /dev/null +++ b/test/scripts/5800-DANE-GnuTLS/5800 @@ -0,0 +1,14 @@ +# DANE client: general +# +gnutls +# +exim -DSERVER=server -bd -oX PORT_D +**** +exim CALLER@test.ex +Testing +**** +exim -qf +**** +killdaemon +exim -DSERVER=server -DNOTDAEMON -qf +**** diff --git a/test/scripts/5800-DANE-GnuTLS/REQUIRES b/test/scripts/5800-DANE-GnuTLS/REQUIRES new file mode 100644 index 000000000..4234c92f8 --- /dev/null +++ b/test/scripts/5800-DANE-GnuTLS/REQUIRES @@ -0,0 +1,3 @@ +support Experimental_DANE +support GnuTLS +running IPv4 diff --git a/test/scripts/5850-DANE-OpenSSL/5850 b/test/scripts/5850-DANE-OpenSSL/5850 new file mode 100644 index 000000000..419930e11 --- /dev/null +++ b/test/scripts/5850-DANE-OpenSSL/5850 @@ -0,0 +1,12 @@ +# DANE client: general +# +exim -DSERVER=server -bd -oX PORT_D +**** +exim CALLER@test.ex +Testing +**** +exim -qf +**** +killdaemon +exim -DSERVER=server -DNOTDAEMON -qf +**** diff --git a/test/scripts/5850-DANE-OpenSSL/REQUIRES b/test/scripts/5850-DANE-OpenSSL/REQUIRES new file mode 100644 index 000000000..59cb7dc91 --- /dev/null +++ b/test/scripts/5850-DANE-OpenSSL/REQUIRES @@ -0,0 +1,3 @@ +support Experimental_DANE +support OpenSSL +running IPv4 -- cgit v1.2.3 From 7a31d6431bfe3bce4da48ac8774e3b0e3c038fc7 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Fri, 8 Aug 2014 21:37:22 +0100 Subject: Test development --- doc/doc-txt/experimental-spec.txt | 7 +++---- src/src/tls-openssl.c | 19 ++++++------------- src/src/transports/smtp.c | 30 +++++++++++++++++++++++------- src/src/transports/smtp.h | 1 + src/src/verify.c | 22 +++++++++++++++++----- test/confs/5850 | 3 +++ test/scripts/5850-DANE-OpenSSL/5850 | 2 +- 7 files changed, 54 insertions(+), 30 deletions(-) (limited to 'src') diff --git a/doc/doc-txt/experimental-spec.txt b/doc/doc-txt/experimental-spec.txt index 333307b74..6eeb5092c 100644 --- a/doc/doc-txt/experimental-spec.txt +++ b/doc/doc-txt/experimental-spec.txt @@ -1224,10 +1224,9 @@ for fast revocation of certificates (which would otherwise be limited by the DNS TTL on the TLSA records). -For client-side DANE there is a new smtp transport option, -hosts_try_dane. It does the obvious thing. -[ may add a hosts_require_dane, too? ] -[ should it be domain-based rather than host-based? ] +For client-side DANE there are two new smtp transport options, +hosts_try_dane and hosts_require_dane. They do the obvious thing. +[ should they be domain-based rather than host-based? ] DANE will only be usable if the target host has DNSSEC-secured MX, A and TLSA records. diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index 201636db0..0bd23ac63 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -1618,15 +1618,8 @@ BOOL dane_required; #endif #ifdef EXPERIMENTAL_DANE -/*XXX TBD: test for transport options, and for TLSA records */ -/*dane = TRUE;*/ - -# ifdef notyet dane_required = verify_check_this_host(&ob->hosts_require_dane, NULL, host->name, host->address, NULL) == OK; -# else -dane_required = FALSE; -#endif if (host->dnssec == DS_YES) { @@ -1637,11 +1630,10 @@ if (host->dnssec == DS_YES) { /* move this out to host.c given the similarity to dns_lookup() ? */ uschar buffer[300]; - int prefix_length; /* why do we want this? */ uschar * fullname = buffer; /* TLSA lookup string */ - (void)sprintf(CS buffer, "_%d._tcp.%n%.256s", host->port, &prefix_length, + (void)sprintf(CS buffer, "_%d._tcp.%.256s", host->port, host->name); switch (rc = dns_lookup(&tlsa_dnsa, buffer, T_TLSA, &fullname)) @@ -1653,7 +1645,7 @@ if (host->dnssec == DS_YES) case DNS_FAIL: if (dane_required) { - /* log that TLSA lookup failed */ + log_write(0, LOG_MAIN, "DANE error: TLSA lookup failed"); return FAIL; } break; @@ -1661,7 +1653,7 @@ if (host->dnssec == DS_YES) case DNS_SUCCEED: if (!dns_is_secure(&tlsa_dnsa)) { - /*log it - tlsa should never be non-dnssec */ + log_write(0, LOG_MAIN, "DANE error: TLSA lookup not DNSSEC"); return DEFER; } dane = TRUE; @@ -1669,9 +1661,10 @@ if (host->dnssec == DS_YES) } } } -else if (dane_required && !dane) +else if (dane_required) { - /* log that dnssec pre-req failed. Hmm - what? */ + /* Hmm - what lookup, precisely? */ + log_write(0, LOG_MAIN, "DANE error: previous lookup not DNSSEC"); return FAIL; } diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index 2e3a6ced4..9abc69d51 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,7 +122,7 @@ optionlist smtp_transport_options[] = { #endif { "hosts_try_auth", opt_stringptr, (void *)offsetof(smtp_transport_options_block, hosts_try_auth) }, -#ifdef EXPERIMENTAL_DANE +#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE) { "hosts_try_dane", opt_stringptr, (void *)offsetof(smtp_transport_options_block, hosts_try_dane) }, #endif @@ -206,6 +210,7 @@ smtp_transport_options_block smtp_transport_option_defaults = { NULL, /* hosts_require_auth */ #ifdef EXPERIMENTAL_DANE NULL, /* hosts_try_dane */ + NULL, /* hosts_require_dane */ #endif #ifndef DISABLE_PRDR NULL, /* hosts_try_prdr */ @@ -1571,8 +1576,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", @@ -3268,10 +3278,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); diff --git a/src/src/transports/smtp.h b/src/src/transports/smtp.h index 018f9cfef..d968a4d54 100644 --- a/src/src/transports/smtp.h +++ b/src/src/transports/smtp.h @@ -23,6 +23,7 @@ typedef struct { uschar *hosts_require_auth; #ifdef EXPERIMENTAL_DANE uschar *hosts_try_dane; + uschar *hosts_require_dane; #endif #ifndef DISABLE_PRDR uschar *hosts_try_prdr; diff --git a/src/src/verify.c b/src/src/verify.c index b1b9f29a4..c2ee47892 100644 --- a/src/src/verify.c +++ b/src/src/verify.c @@ -644,9 +644,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); log_write(0, LOG_MAIN, "TLS session failure: delivering unencrypted " @@ -676,8 +683,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", diff --git a/test/confs/5850 b/test/confs/5850 index ac967fcb8..53cb78ae1 100644 --- a/test/confs/5850 +++ b/test/confs/5850 @@ -60,6 +60,9 @@ send_to_server: # tls_privatekey = DIR/aux-fixed/cert2 # tls_verify_certificates = DIR/aux-fixed/cert2 +# hosts_try_dane = * + hosts_require_dane = * + # ----- Retry ----- diff --git a/test/scripts/5850-DANE-OpenSSL/5850 b/test/scripts/5850-DANE-OpenSSL/5850 index 419930e11..0776fb6b4 100644 --- a/test/scripts/5850-DANE-OpenSSL/5850 +++ b/test/scripts/5850-DANE-OpenSSL/5850 @@ -5,7 +5,7 @@ exim -DSERVER=server -bd -oX PORT_D exim CALLER@test.ex Testing **** -exim -qf +exim -d+all -qf **** killdaemon exim -DSERVER=server -DNOTDAEMON -qf -- cgit v1.2.3 From cf2b569e3a2f8956b7045191e96bc5edfd366c78 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Sun, 10 Aug 2014 11:49:49 +0100 Subject: On a host lookup name->MX->A->ip sequence, require both stages to be dnssec before declaring the lookup was secure. --- src/src/host.c | 51 ++++++++++++++++++++++++++++++++++----------------- src/src/tls-openssl.c | 1 + test/confs/5850 | 8 +++++--- 3 files changed, 40 insertions(+), 20 deletions(-) (limited to 'src') 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/tls-openssl.c b/src/src/tls-openssl.c index 0bd23ac63..eb74605da 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -1664,6 +1664,7 @@ if (host->dnssec == DS_YES) else if (dane_required) { /* Hmm - what lookup, precisely? */ + /*XXX a shame we only find this after making tcp & smtp connection */ log_write(0, LOG_MAIN, "DANE error: previous lookup not DNSSEC"); return FAIL; } diff --git a/test/confs/5850 b/test/confs/5850 index 53cb78ae1..0b132e29e 100644 --- a/test/confs/5850 +++ b/test/confs/5850 @@ -37,9 +37,11 @@ tls_privatekey = ${if eq {SERVER}{server}{DIR/aux-fixed/cert1}fail} begin routers client: - driver = accept + driver = dnslookup condition = ${if eq {SERVER}{server}{no}{yes}} - retry_use_local_part +# retry_use_local_part + dnssec_request_domains = * + self = send transport = send_to_server server: @@ -54,7 +56,7 @@ begin transports send_to_server: driver = smtp allow_localhost - hosts = 127.0.0.1 +# hosts = 127.0.0.1 port = PORT_D # tls_certificate = DIR/aux-fixed/cert2 # tls_privatekey = DIR/aux-fixed/cert2 -- cgit v1.2.3 From b4161d10ee4c4eb7fd61224d827cc89726e2d8f8 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Sun, 10 Aug 2014 12:31:21 +0100 Subject: Add support in the fakens utility for TLSA records --- src/src/dane-openssl.c | 6 ++-- src/src/dns.c | 2 +- src/src/tls-openssl.c | 12 +++---- test/dnszones-src/db.test.ex | 3 ++ test/log/5850 | 2 +- test/src/fakens.c | 74 +++++++++++++++++++++++++++++++------------- 6 files changed, 67 insertions(+), 32 deletions(-) (limited to 'src') diff --git a/src/src/dane-openssl.c b/src/src/dane-openssl.c index aee6323bf..4f90caa4a 100644 --- a/src/src/dane-openssl.c +++ b/src/src/dane-openssl.c @@ -1162,7 +1162,8 @@ dane_cert_list xlist = 0; dane_pkey_list klist = 0; const EVP_MD *md = 0; -DEBUG(D_tls) debug_printf("Dane add_tlsa\n"); +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))) { @@ -1340,7 +1341,7 @@ if(sctx->app_verify_callback != verify_cert) return -1; } #else -DEBUG(D_tls) debug_printf("Dane ssl_init\n"); +DEBUG(D_tls) debug_printf("Dane ssl-init\n"); if(dane_idx < 0) { DANEerr(DANE_F_SSL_DANE_INIT, DANE_R_LIBRARY_INIT); @@ -1362,7 +1363,6 @@ if(!SSL_set_ex_data(ssl, dane_idx, dane)) OPENSSL_free(dane); return 0; } -DEBUG(D_tls) debug_printf("Dane ssl-init: new dane struct: %p\n", dane); dane->verify = 0; dane->hosts = 0; 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/tls-openssl.c b/src/src/tls-openssl.c index eb74605da..70ac63f16 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -1663,8 +1663,8 @@ if (host->dnssec == DS_YES) } else if (dane_required) { - /* Hmm - what lookup, precisely? */ /*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; } @@ -1714,9 +1714,9 @@ if (expciphers != NULL) if (dane) { if (!DANESSL_library_init()) - return tls_error(US"library init", host, US"DANE library error"); + return tls_error(US"library init", host, NULL); if (DANESSL_CTX_init(client_ctx) <= 0) - return tls_error(US"context init", host, US"DANE library error"); + return tls_error(US"context init", host, NULL); } else @@ -1777,7 +1777,7 @@ if (dane) uschar * hostnames[2] = { host->name, NULL }; if (DANESSL_init(client_ssl, NULL, hostnames) != 1) - return tls_error(US"hostnames load", host, US"DANE library error"); + return tls_error(US"hostnames load", host, NULL); for (rr = dns_next_rr(&tlsa_dnsa, &dnss, RESET_ANSWERS); rr; @@ -1805,8 +1805,8 @@ if (dane) mdname, p, rr->size - (p - rr->data))) { default: - case 0: /* action not taken; log error */ - return FAIL; + case 0: /* action not taken */ + return tls_error(US"tlsa load", host, NULL); case 1: break; } } diff --git a/test/dnszones-src/db.test.ex b/test/dnszones-src/db.test.ex index c65baa470..bd334918b 100644 --- a/test/dnszones-src/db.test.ex +++ b/test/dnszones-src/db.test.ex @@ -77,10 +77,13 @@ badloop A V4NET.0.0.1 v6 AAAA V6NET:ffff:836f:0a00:000a:0800:200a:c032 ; Alias A and CNAME records for the local host, under the name "eximtesthost" +; Make the A covered by DNSSEC and add a TLSA for it. DNSSEC eximtesthost A HOSTIPV4 alias-eximtesthost CNAME eximtesthost.test.ex. +DNSSEC _1225._tcp.eximtesthost TLSA 3 1 2 f000baaa + ; A bad CNAME badcname CNAME rhubarb.test.ex. diff --git a/test/log/5850 b/test/log/5850 index f0432dd7e..e8b37bb61 100644 --- a/test/log/5850 +++ b/test/log/5850 @@ -1,6 +1,6 @@ 1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss 1999-03-02 09:44:33 Start queue run: pid=pppp -qf -1999-03-02 09:44:33 10HmaX-0005vi-00 DANE error: TLSA lookup failed +1999-03-02 09:44:33 10HmaX-0005vi-00 TLS error on connection to eximtesthost.test.ex [ip4.ip4.ip4.ip4] (tlsa load): error:8006C067:DANE library:func(108):Bad TLSA record digest 1999-03-02 09:44:33 10HmaX-0005vi-00 == CALLER@mxplain.test.ex R=client T=send_to_server defer (-37): failure while setting up TLS session 1999-03-02 09:44:33 End queue run: pid=pppp -qf diff --git a/test/src/fakens.c b/test/src/fakens.c index ec7588cc8..912f41984 100644 --- a/test/src/fakens.c +++ b/test/src/fakens.c @@ -99,21 +99,25 @@ not defined, assume we are in this state. A really old system might not even know about AAAA and SRV at all. */ #ifndef ns_t_a -#define ns_t_a T_A -#define ns_t_ns T_NS -#define ns_t_cname T_CNAME -#define ns_t_soa T_SOA -#define ns_t_ptr T_PTR -#define ns_t_mx T_MX -#define ns_t_txt T_TXT -#define ns_t_aaaa T_AAAA -#define ns_t_srv T_SRV -#ifndef T_AAAA -#define T_AAAA 28 -#endif -#ifndef T_SRV -#define T_SRV 33 -#endif +# define ns_t_a T_A +# define ns_t_ns T_NS +# define ns_t_cname T_CNAME +# define ns_t_soa T_SOA +# define ns_t_ptr T_PTR +# define ns_t_mx T_MX +# define ns_t_txt T_TXT +# define ns_t_aaaa T_AAAA +# define ns_t_srv T_SRV +# define ns_t_tlsa T_TLSA +# ifndef T_AAAA +# define T_AAAA 28 +# endif +# ifndef T_SRV +# define T_SRV 33 +# endif +# ifndef T_TLSA +# define T_TLSA 52 +# endif #endif static tlist type_list[] = { @@ -126,6 +130,7 @@ static tlist type_list[] = { { US"TXT", ns_t_txt }, { US"AAAA", ns_t_aaaa }, { US"SRV", ns_t_srv }, + { US"TLSA", ns_t_tlsa }, { NULL, 0 } }; @@ -189,6 +194,20 @@ while (*name != 0) return pk; } +uschar * +shortfield(uschar ** pp, uschar * pk) +{ +unsigned value = 0; +uschar * p = *pp; + +while (isdigit(*p)) value = value*10 + *p++ - '0'; +while (isspace(*p)) p++; +*pp = p; +*pk++ = (value >> 8) & 255; +*pk++ = value & 255; +return pk; +} + /************************************************* @@ -237,7 +256,7 @@ if (typeptr->name == NULL) rrdomain[0] = 0; /* No previous domain */ (void)fseek(f, 0, SEEK_SET); /* Start again at the beginning */ -*dnssec = TRUE; /* cancelled by first nonsecure rec found */ +*dnssec = TRUE; /* cancelled by first nonsecure rec found */ /* Scan for RRs */ @@ -387,11 +406,7 @@ while (fgets(CS buffer, sizeof(buffer), f) != NULL) break; case ns_t_mx: - value = 0; - while (isdigit(*p)) value = value*10 + *p++ - '0'; - while (isspace(*p)) p++; - *pk++ = (value >> 8) & 255; - *pk++ = value & 255; + pk = shortfield(&p, pk); if (ep[-1] != '.') sprintf(ep, "%s.", zone); pk = packname(p, pk); plen = Ustrlen(p); @@ -404,6 +419,23 @@ while (fgets(CS buffer, sizeof(buffer), f) != NULL) *pp = pk - pp - 1; break; + case ns_t_tlsa: + pk = shortfield(&p, pk); /* usage */ + pk = shortfield(&p, pk); /* selector */ + pk = shortfield(&p, pk); /* match type */ + while (isxdigit(*p)) + { + value = toupper(*p) - (isdigit(*p) ? '0' : '7') << 4; + if (isxdigit(*++p)) + { + value |= toupper(*p) - (isdigit(*p) ? '0' : '7'); + p++; + } + *pk++ = value & 255; + } + + break; + case ns_t_srv: for (i = 0; i < 3; i++) { -- cgit v1.2.3 From 401a89359e1fcff59218ae2a05a5e9f3a603d915 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Sun, 10 Aug 2014 15:00:27 +0100 Subject: Verifiable conn with DANE-EE(3) / SPKI(1) / SHA2-512(2) --- DANE-draft-notes | 11 +++++++++++ doc/doc-txt/experimental-spec.txt | 17 +++++++++++++++-- src/src/dane-openssl.c | 4 ++-- src/src/tls-openssl.c | 4 ++-- test/confs/5850 | 10 +--------- test/dnszones-src/db.test.ex | 2 +- test/log/5850 | 11 +++++------ test/scripts/5850-DANE-OpenSSL/5850 | 3 ++- 8 files changed, 39 insertions(+), 23 deletions(-) create mode 100644 DANE-draft-notes (limited to 'src') diff --git a/DANE-draft-notes b/DANE-draft-notes new file mode 100644 index 000000000..21b3992cc --- /dev/null +++ b/DANE-draft-notes @@ -0,0 +1,11 @@ + +draft 11 + +3.1.2 - Para 4 (records with Sel Full(0) are discouraged) +==> There's a matching type Full but not such a Selector type. + Should this be "Cert(0), or Matching Type Full(0)" ? + Suspect the latter. + +3.1.2 Needs a para added regarding certificate date verification, + to contrast with the requirement to NOT check for + DANE-EE defined in 3.1.1 diff --git a/doc/doc-txt/experimental-spec.txt b/doc/doc-txt/experimental-spec.txt index 6eeb5092c..f1414287d 100644 --- a/doc/doc-txt/experimental-spec.txt +++ b/doc/doc-txt/experimental-spec.txt @@ -1181,7 +1181,10 @@ means not having to pay a CA for certificates. DANE requires a server operator to do three things: 1) run DNSSEC. This provides assurance to clients that DNS lookups they do for the server have not -been tampered with. +been tampered with. The domain MX record applying +to this server, its A record, its TLSA record and +any associated CNAME records must all be covered by +DNSSEC. 2) add TLSA DNS records. These say what the server certificate for a TLS connection should be. 3) offer a server certificate, or certificate chain, @@ -1214,7 +1217,17 @@ servers, each having a TLSA query-domain CNAME record, all of which point to a single TLSA record. The TLSA record should have a Selector field of SPKI(1) -and a Matching Type fiels of SHA2-512(2). +and a Matching Type field of SHA2-512(2). + +At the time of writing, https://www.huque.com/bin/gen_tlsa +is useful for quickly generating TLSA records; and commands like + + openssl x509 -in -pubkey -noout /dev/null \ + | openssl sha512 \ + | awk '{print $2}' + +are workable for 4th-field hashes. For use with the DANE_TA model, server certificates must have a correct name (SubjectName or SubjectAltName). diff --git a/src/src/dane-openssl.c b/src/src/dane-openssl.c index 4f90caa4a..aab32cabd 100644 --- a/src/src/dane-openssl.c +++ b/src/src/dane-openssl.c @@ -859,7 +859,7 @@ X509 *cert = ctx->cert; /* XXX: accessor? */ int matched = 0; int chain_length = sk_X509_num(ctx->chain); -DEBUG(D_tls) debug_printf("Dane library verify_chain fn called\n"); +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]; @@ -952,7 +952,7 @@ int (*cb)(int, X509_STORE_CTX *) = ctx->verify_cb; int matched; X509 *cert = ctx->cert; /* XXX: accessor? */ -DEBUG(D_tls) debug_printf("Dane library verify_cert fn called\n"); +DEBUG(D_tls) debug_printf("Dane verify_cert\n"); if(ssl_idx < 0) ssl_idx = SSL_get_ex_data_X509_STORE_CTX_idx(); diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index 70ac63f16..001403494 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -1796,8 +1796,8 @@ if (dane) { default: /* log bad */ return FAIL; case 0: mdname = NULL; break; - case 1: mdname = "SHA2-256"; break; - case 2: mdname = "SHA2-512"; break; + case 1: mdname = "sha256"; break; + case 2: mdname = "sha512"; break; } switch (DANESSL_add_tlsa(client_ssl, diff --git a/test/confs/5850 b/test/confs/5850 index 0b132e29e..cd4ccc59f 100644 --- a/test/confs/5850 +++ b/test/confs/5850 @@ -26,10 +26,7 @@ tls_advertise_hosts = * # Set certificate only if server tls_certificate = ${if eq {SERVER}{server}{DIR/aux-fixed/cert1}fail} -tls_privatekey = ${if eq {SERVER}{server}{DIR/aux-fixed/cert1}fail} - -#tls_verify_hosts = * -#tls_verify_certificates = ${if eq {SERVER}{server}{DIR/aux-fixed/cert2}fail} +#tls_privatekey = ${if eq {SERVER}{server}{DIR/aux-fixed/cert1}fail} # ----- Routers ----- @@ -39,7 +36,6 @@ begin routers client: driver = dnslookup condition = ${if eq {SERVER}{server}{no}{yes}} -# retry_use_local_part dnssec_request_domains = * self = send transport = send_to_server @@ -56,11 +52,7 @@ begin transports send_to_server: driver = smtp allow_localhost -# hosts = 127.0.0.1 port = PORT_D -# tls_certificate = DIR/aux-fixed/cert2 -# tls_privatekey = DIR/aux-fixed/cert2 -# tls_verify_certificates = DIR/aux-fixed/cert2 # hosts_try_dane = * hosts_require_dane = * diff --git a/test/dnszones-src/db.test.ex b/test/dnszones-src/db.test.ex index bd334918b..fed44daf9 100644 --- a/test/dnszones-src/db.test.ex +++ b/test/dnszones-src/db.test.ex @@ -82,7 +82,7 @@ v6 AAAA V6NET:ffff:836f:0a00:000a:0800:200a:c032 DNSSEC eximtesthost A HOSTIPV4 alias-eximtesthost CNAME eximtesthost.test.ex. -DNSSEC _1225._tcp.eximtesthost TLSA 3 1 2 f000baaa +DNSSEC _1225._tcp.eximtesthost TLSA 3 1 2 3d5eb81b1dfc3f93c1fa8819e3fb3fdb41bb590441d5f3811db17772f4bc6de29bdd7c4f4b723750dda871b99379192b3f979f03db1252c4f08b03ef7176528d ; A bad CNAME diff --git a/test/log/5850 b/test/log/5850 index e8b37bb61..aad1ade98 100644 --- a/test/log/5850 +++ b/test/log/5850 @@ -1,14 +1,13 @@ 1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss 1999-03-02 09:44:33 Start queue run: pid=pppp -qf -1999-03-02 09:44:33 10HmaX-0005vi-00 TLS error on connection to eximtesthost.test.ex [ip4.ip4.ip4.ip4] (tlsa load): error:8006C067:DANE library:func(108):Bad TLSA record digest -1999-03-02 09:44:33 10HmaX-0005vi-00 == CALLER@mxplain.test.ex R=client T=send_to_server defer (-37): failure while setting up TLS session +1999-03-02 09:44:33 10HmaX-0005vi-00 => CALLER@mxplain.test.ex R=client T=send_to_server H=eximtesthost.test.ex [ip4.ip4.ip4.ip4] X=TLSv1:AES256-SHA:256 DN="/C=UK/O=The Exim Maintainers/OU=Test Suite/CN=Phil Pennock" C="250 OK id=10HmaY-0005vi-00" +1999-03-02 09:44:33 10HmaX-0005vi-00 Completed 1999-03-02 09:44:33 End queue run: pid=pppp -qf ******** SERVER ******** 1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225 -1999-03-02 09:44:33 TLS error on connection from the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] (SSL_accept): error: <> -1999-03-02 09:44:33 TLS client disconnected cleanly (rejected our certificate?) +1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@myhost.test.ex H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtps X=TLSv1:AES256-SHA:256 S=sss id=E10HmaX-0005vi-00@myhost.test.ex 1999-03-02 09:44:33 Start queue run: pid=pppp -qf -1999-03-02 09:44:33 10HmaX-0005vi-00 => :blackhole: R=server -1999-03-02 09:44:33 10HmaX-0005vi-00 Completed +1999-03-02 09:44:33 10HmaY-0005vi-00 => :blackhole: R=server +1999-03-02 09:44:33 10HmaY-0005vi-00 Completed 1999-03-02 09:44:33 End queue run: pid=pppp -qf diff --git a/test/scripts/5850-DANE-OpenSSL/5850 b/test/scripts/5850-DANE-OpenSSL/5850 index 963bf4f4e..449a75bf6 100644 --- a/test/scripts/5850-DANE-OpenSSL/5850 +++ b/test/scripts/5850-DANE-OpenSSL/5850 @@ -2,10 +2,11 @@ # exim -DSERVER=server -bd -oX PORT_D **** +# TLSA (3 1 2) exim CALLER@mxplain.test.ex Testing **** -exim -d+all -qf +exim -qf **** killdaemon exim -DSERVER=server -DNOTDAEMON -qf -- cgit v1.2.3 From e5cccda9bbf169ea7dc97fa3859735523dd4cec0 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Sun, 10 Aug 2014 16:57:15 +0100 Subject: Capture the knowlege that verification succeeded --- src/src/dane-openssl.c | 7 +++---- src/src/tls-openssl.c | 27 +++++++++++++++++++++++++-- test/confs/5850 | 2 +- test/log/5850 | 12 ++++++------ 4 files changed, 35 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/src/dane-openssl.c b/src/src/dane-openssl.c index aab32cabd..2430d475f 100644 --- a/src/src/dane-openssl.c +++ b/src/src/dane-openssl.c @@ -859,7 +859,7 @@ 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"); +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]; @@ -952,7 +952,7 @@ 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"); +DEBUG(D_tls) debug_printf("Dane verify-cert\n"); if(ssl_idx < 0) ssl_idx = SSL_get_ex_data_X509_STORE_CTX_idx(); @@ -1084,7 +1084,7 @@ DANESSL_cleanup(SSL *ssl) ssl_dane *dane; int u; -DEBUG(D_tls) debug_printf("Dane library cleanup fn called\n"); +DEBUG(D_tls) debug_printf("Dane lib-cleanup\n"); if(dane_idx < 0 || !(dane = SSL_get_ex_data(ssl, dane_idx))) return; @@ -1106,7 +1106,6 @@ if(dane->roots) if(dane->chain) sk_X509_pop_free(dane->chain, X509_free); OPENSSL_free(dane); -DEBUG(D_tls) debug_printf("Dane library cleanup fn return\n"); } static dane_host_list diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index 001403494..e37b1add5 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -361,7 +361,7 @@ else return 0; /* reject */ } # endif -#endif +#endif /*EXPERIMENTAL_CERTNAMES*/ DEBUG(D_tls) debug_printf("SSL%s verify ok: depth=0 SN=%s\n", *calledp ? "" : " authenticated", txt); @@ -385,6 +385,28 @@ 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]; + +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); + +if (state == 1) + tls_out.certificate_verified = TRUE; +return 1; +} +#endif + /************************************************* * Information callback * @@ -999,7 +1021,6 @@ return i; #endif /*!DISABLE_OCSP*/ - /************************************************* * Initialize for TLS * *************************************************/ @@ -1713,6 +1734,8 @@ if (expciphers != NULL) #ifdef EXPERIMENTAL_DANE if (dane) { + 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) diff --git a/test/confs/5850 b/test/confs/5850 index cd4ccc59f..b2c7cb7eb 100644 --- a/test/confs/5850 +++ b/test/confs/5850 @@ -16,7 +16,7 @@ gecos_name = CALLER_NAME acl_smtp_rcpt = accept -log_selector = +tls_peerdn +log_selector = +received_recipients +tls_peerdn +tls_certificate_verified queue_only queue_run_in_order diff --git a/test/log/5850 b/test/log/5850 index 568396d01..7266ec26a 100644 --- a/test/log/5850 +++ b/test/log/5850 @@ -1,16 +1,16 @@ -1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss -1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss +1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss for CALLER@dane256ee.test.ex +1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss for CALLER@mxdane512ee.test.ex 1999-03-02 09:44:33 Start queue run: pid=pppp -qf -1999-03-02 09:44:33 10HmaX-0005vi-00 => CALLER@dane256ee.test.ex R=client T=send_to_server H=dane256ee.test.ex [ip4.ip4.ip4.ip4] X=TLSv1:AES256-SHA:256 DN="/C=UK/O=The Exim Maintainers/OU=Test Suite/CN=Phil Pennock" C="250 OK id=10HmaZ-0005vi-00" +1999-03-02 09:44:33 10HmaX-0005vi-00 => CALLER@dane256ee.test.ex R=client T=send_to_server H=dane256ee.test.ex [ip4.ip4.ip4.ip4] X=TLSv1:AES256-SHA:256 CV=yes DN="/C=UK/O=The Exim Maintainers/OU=Test Suite/CN=Phil Pennock" C="250 OK id=10HmaZ-0005vi-00" 1999-03-02 09:44:33 10HmaX-0005vi-00 Completed -1999-03-02 09:44:33 10HmaY-0005vi-00 => CALLER@mxdane512ee.test.ex R=client T=send_to_server H=dane512ee.test.ex [ip4.ip4.ip4.ip4] X=TLSv1:AES256-SHA:256 DN="/C=UK/O=The Exim Maintainers/OU=Test Suite/CN=Phil Pennock" C="250 OK id=10HmbA-0005vi-00" +1999-03-02 09:44:33 10HmaY-0005vi-00 => CALLER@mxdane512ee.test.ex R=client T=send_to_server H=dane512ee.test.ex [ip4.ip4.ip4.ip4] X=TLSv1:AES256-SHA:256 CV=yes DN="/C=UK/O=The Exim Maintainers/OU=Test Suite/CN=Phil Pennock" C="250 OK id=10HmbA-0005vi-00" 1999-03-02 09:44:33 10HmaY-0005vi-00 Completed 1999-03-02 09:44:33 End queue run: pid=pppp -qf ******** SERVER ******** 1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225 -1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@myhost.test.ex H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtps X=TLSv1:AES256-SHA:256 S=sss id=E10HmaX-0005vi-00@myhost.test.ex -1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@myhost.test.ex H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtps X=TLSv1:AES256-SHA:256 S=sss id=E10HmaY-0005vi-00@myhost.test.ex +1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@myhost.test.ex H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtps X=TLSv1:AES256-SHA:256 CV=no S=sss id=E10HmaX-0005vi-00@myhost.test.ex for CALLER@dane256ee.test.ex +1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@myhost.test.ex H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtps X=TLSv1:AES256-SHA:256 CV=no S=sss id=E10HmaY-0005vi-00@myhost.test.ex for CALLER@mxdane512ee.test.ex 1999-03-02 09:44:33 Start queue run: pid=pppp -qf 1999-03-02 09:44:33 10HmaZ-0005vi-00 => :blackhole: R=server 1999-03-02 09:44:33 10HmaZ-0005vi-00 Completed -- cgit v1.2.3 From 53a7196b578115484068f8c13326741824002c32 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Sun, 10 Aug 2014 17:25:26 +0100 Subject: Change CV= log line element for dane-verified cert --- src/src/deliver.c | 11 ++++++++++- src/src/globals.h | 3 +++ src/src/spool_in.c | 3 +++ src/src/structs.h | 3 +++ src/src/tls-openssl.c | 11 ++++++++++- test/log/5850 | 4 ++-- 6 files changed, 31 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/src/deliver.c b/src/src/deliver.c index b0b4601dc..ebd06b504 100644 --- a/src/src/deliver.c +++ b/src/src/deliver.c @@ -697,7 +697,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"\""); @@ -4125,6 +4133,7 @@ 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); + if (tls_out.dane_verified) setflag(addr, af_dane_verified); /* Use an X item only if there's something to send */ #ifdef SUPPORT_TLS diff --git a/src/src/globals.h b/src/src/globals.h index 32ddd16e2..654114848 100644 --- a/src/src/globals.h +++ b/src/src/globals.h @@ -82,6 +82,9 @@ 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 */ +#endif uschar *cipher; /* Cipher used */ BOOL on_connect; /* For older MTAs that don't STARTTLS */ uschar *on_connect_ports; /* Ports always tls-on-connect */ diff --git a/src/src/spool_in.c b/src/src/spool_in.c index 6dcb512e4..f53251a86 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; diff --git a/src/src/structs.h b/src/src/structs.h index 71ac5d8e3..27b73e903 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 e37b1add5..c05253f73 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -386,6 +386,7 @@ 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. */ @@ -402,10 +403,12 @@ tls_out.peerdn = txt; tls_out.peercert = X509_dup(cert); if (state == 1) + tls_out.dane_verified = tls_out.certificate_verified = TRUE; return 1; } -#endif + +#endif /*EXPERIMENTAL_DANE*/ /************************************************* @@ -1442,6 +1445,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) @@ -1712,6 +1718,9 @@ rc = tls_init(&client_ctx, host, NULL, if (rc != OK) return rc; tls_out.certificate_verified = FALSE; +#ifdef EXPERIMENTAL_DANE +tls_out.dane_verified = FALSE; +#endif client_verify_callback_called = FALSE; if (!expand_check(ob->tls_require_ciphers, US"tls_require_ciphers", diff --git a/test/log/5850 b/test/log/5850 index 7266ec26a..498137321 100644 --- a/test/log/5850 +++ b/test/log/5850 @@ -1,9 +1,9 @@ 1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss for CALLER@dane256ee.test.ex 1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss for CALLER@mxdane512ee.test.ex 1999-03-02 09:44:33 Start queue run: pid=pppp -qf -1999-03-02 09:44:33 10HmaX-0005vi-00 => CALLER@dane256ee.test.ex R=client T=send_to_server H=dane256ee.test.ex [ip4.ip4.ip4.ip4] X=TLSv1:AES256-SHA:256 CV=yes DN="/C=UK/O=The Exim Maintainers/OU=Test Suite/CN=Phil Pennock" C="250 OK id=10HmaZ-0005vi-00" +1999-03-02 09:44:33 10HmaX-0005vi-00 => CALLER@dane256ee.test.ex R=client T=send_to_server H=dane256ee.test.ex [ip4.ip4.ip4.ip4] X=TLSv1:AES256-SHA:256 CV=dane DN="/C=UK/O=The Exim Maintainers/OU=Test Suite/CN=Phil Pennock" C="250 OK id=10HmaZ-0005vi-00" 1999-03-02 09:44:33 10HmaX-0005vi-00 Completed -1999-03-02 09:44:33 10HmaY-0005vi-00 => CALLER@mxdane512ee.test.ex R=client T=send_to_server H=dane512ee.test.ex [ip4.ip4.ip4.ip4] X=TLSv1:AES256-SHA:256 CV=yes DN="/C=UK/O=The Exim Maintainers/OU=Test Suite/CN=Phil Pennock" C="250 OK id=10HmbA-0005vi-00" +1999-03-02 09:44:33 10HmaY-0005vi-00 => CALLER@mxdane512ee.test.ex R=client T=send_to_server H=dane512ee.test.ex [ip4.ip4.ip4.ip4] X=TLSv1:AES256-SHA:256 CV=dane DN="/C=UK/O=The Exim Maintainers/OU=Test Suite/CN=Phil Pennock" C="250 OK id=10HmbA-0005vi-00" 1999-03-02 09:44:33 10HmaY-0005vi-00 Completed 1999-03-02 09:44:33 End queue run: pid=pppp -qf -- cgit v1.2.3 From eeb9276b22cd991157c46a068a85ffe59b948d75 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Sun, 10 Aug 2014 21:52:24 +0100 Subject: Enable OCSP --- doc/doc-txt/experimental-spec.txt | 8 +++++--- src/src/tls-openssl.c | 1 - 2 files changed, 5 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/doc/doc-txt/experimental-spec.txt b/doc/doc-txt/experimental-spec.txt index f1414287d..b1b89e007 100644 --- a/doc/doc-txt/experimental-spec.txt +++ b/doc/doc-txt/experimental-spec.txt @@ -1234,7 +1234,8 @@ must have a correct name (SubjectName or SubjectAltName). The use of OCSP-stapling should be considered, allowing for fast revocation of certificates (which would otherwise -be limited by the DNS TTL on the TLSA records). +be limited by the DNS TTL on the TLSA records). However, +this is likely to only be usable with DANE_TA. For client-side DANE there are two new smtp transport options, @@ -1252,12 +1253,13 @@ If dane is in use the following transport options are ignored: tls_verify_certificates tls_crl tls_verify_cert_hostnames - hosts_require_ocsp (might rethink those two) - hosts_request_ocsp Currently dnssec_request_domains must be active (need to think about that) and dnssec_require_domains is ignored. +If verification was successful using DANE then the "CV" item +in the delivery log line will show as "CV=dane". + -------------------------------------------------------------- End of file diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index c05253f73..1ec7786bd 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -1696,7 +1696,6 @@ else if (dane_required) return FAIL; } -if (!dane) /*XXX todo: enable ocsp with dane */ #endif #ifndef DISABLE_OCSP -- cgit v1.2.3 From a0a2d1eea23b7fd28d4fc85672ed3210926bdbab Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Thu, 14 Aug 2014 14:52:40 +0100 Subject: Fix non-dane build --- src/OS/Makefile-Base | 6 +++--- src/src/deliver.c | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/OS/Makefile-Base b/src/OS/Makefile-Base index 8937f3cad..f82549ded 100644 --- a/src/OS/Makefile-Base +++ b/src/OS/Makefile-Base @@ -541,7 +541,6 @@ acl.o: $(HDRS) acl.c child.o: $(HDRS) child.c crypt16.o: $(HDRS) crypt16.c daemon.o: $(HDRS) daemon.c -dane.o: $(HDRS) dane.c dane-gnu.c dane-openssl.c dbfn.o: $(HDRS) dbfn.c debug.o: $(HDRS) debug.c deliver.o: $(HDRS) deliver.c @@ -603,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/src/deliver.c b/src/src/deliver.c index ebd06b504..86deceb76 100644 --- a/src/src/deliver.c +++ b/src/src/deliver.c @@ -4133,7 +4133,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 -- cgit v1.2.3 From 36b894a60b9431d20a8b8b1aa557673c747c4b47 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Thu, 14 Aug 2014 21:21:45 +0100 Subject: Fix fakens TLSA generation and DANE TLSA lookup --- src/src/tls-openssl.c | 18 ++++++++++++++---- test/src/fakens.c | 19 ++++++++++++++++--- test/stdout/5800 | 2 +- 3 files changed, 31 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index 1ec7786bd..79beffadf 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -1806,6 +1806,7 @@ if (dane) dns_record * rr; dns_scan dnss; uschar * hostnames[2] = { host->name, NULL }; + int found = 0; if (DANESSL_init(client_ssl, NULL, hostnames) != 1) return tls_error(US"hostnames load", host, NULL); @@ -1819,13 +1820,16 @@ if (dane) int usage, selector, mtype; const char * mdname; - GETSHORT(usage, p); - GETSHORT(selector, p); - GETSHORT(mtype, p); + found++; + usage = *p++; + selector = *p++; + mtype = *p++; switch (mtype) { - default: /* log bad */ return FAIL; + 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; @@ -1841,6 +1845,12 @@ if (dane) case 1: break; } } + + if (!found) + { + log_write(0, LOG_MAIN, "DANE error: No TLSA records"); + return FAIL; + } } #endif diff --git a/test/src/fakens.c b/test/src/fakens.c index 912f41984..fd3604a3c 100644 --- a/test/src/fakens.c +++ b/test/src/fakens.c @@ -194,6 +194,19 @@ while (*name != 0) return pk; } +uschar * +bytefield(uschar ** pp, uschar * pk) +{ +unsigned value = 0; +uschar * p = *pp; + +while (isdigit(*p)) value = value*10 + *p++ - '0'; +while (isspace(*p)) p++; +*pp = p; +*pk++ = value & 255; +return pk; +} + uschar * shortfield(uschar ** pp, uschar * pk) { @@ -420,9 +433,9 @@ while (fgets(CS buffer, sizeof(buffer), f) != NULL) break; case ns_t_tlsa: - pk = shortfield(&p, pk); /* usage */ - pk = shortfield(&p, pk); /* selector */ - pk = shortfield(&p, pk); /* match type */ + pk = bytefield(&p, pk); /* usage */ + pk = bytefield(&p, pk); /* selector */ + pk = bytefield(&p, pk); /* match type */ while (isxdigit(*p)) { value = toupper(*p) - (isdigit(*p) ? '0' : '7') << 4; diff --git a/test/stdout/5800 b/test/stdout/5800 index bcbbd88e0..b9c64fea0 100644 --- a/test/stdout/5800 +++ b/test/stdout/5800 @@ -1,4 +1,4 @@ > -> dnslookup tlsa: 3 1 2 3d5eb81b1dfc3f93c1fa8819e3fb3fdb41bb590441d5f3811db17772f4bc6de29bdd7c4f4b723750dda871b99379192b3f979f03db1252c4f08b03ef7176528d000000 +> dnslookup tlsa: 3 1 2 3d5eb81b1dfc3f93c1fa8819e3fb3fdb41bb590441d5f3811db17772f4bc6de29bdd7c4f4b723750dda871b99379192b3f979f03db1252c4f08b03ef7176528d > > -- cgit v1.2.3 From 85098ee7ec9a0201b9d0df7dff202506097420b1 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Fri, 15 Aug 2014 20:24:44 +0100 Subject: Compiler quietening --- src/src/dane-openssl.c | 8 ++++++-- src/src/deliver.c | 2 ++ src/src/expand.c | 4 ++-- src/src/globals.c | 6 ++++++ src/src/spool_in.c | 2 +- src/src/tls-openssl.c | 6 +++++- 6 files changed, 22 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/src/dane-openssl.c b/src/src/dane-openssl.c index 2430d475f..6345b39ca 100644 --- a/src/src/dane-openssl.c +++ b/src/src/dane-openssl.c @@ -202,9 +202,9 @@ for(matched = 0; !matched && slist; slist = slist->next) { dane_mtype_list m; unsigned char mdbuf[EVP_MAX_MD_SIZE]; - unsigned char *buf; + unsigned char *buf = NULL; unsigned char *buf2; - unsigned int len; + unsigned int len = 0; /* * Extract ASN.1 DER form of certificate or public key. @@ -679,6 +679,7 @@ 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); @@ -687,6 +688,7 @@ if(matched > 0) DANEerr(DANE_F_CHECK_END_ENTITY, ERR_R_MALLOC_FAILURE); return -1; } + } return matched; } @@ -714,12 +716,14 @@ for(hosts = dane->hosts; hosts; hosts = hosts->next) * 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 diff --git a/src/src/deliver.c b/src/src/deliver.c index 86deceb76..ab0815ed4 100644 --- a/src/src/deliver.c +++ b/src/src/deliver.c @@ -7001,12 +7001,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/expand.c b/src/src/expand.c index 70d7c7d2f..e5af63d89 100644 --- a/src/src/expand.c +++ b/src/src/expand.c @@ -6368,14 +6368,14 @@ while (*s != 0) case EOP_UTF8CLEAN: { - int seq_len, index = 0; + int seq_len = 0, index = 0; int bytes_left = 0; uschar seq_buff[4]; /* accumulate utf-8 here */ while (*sub != 0) { int complete; - long codepoint; + long codepoint = 0; uschar c; complete = 0; diff --git a/src/src/globals.c b/src/src/globals.c index 7d4ab63a1..d09903d65 100644 --- a/src/src/globals.c +++ b/src/src/globals.c @@ -103,6 +103,9 @@ tls_support tls_in = { -1, /* tls_active */ 0, /* tls_bits */ FALSE,/* tls_certificate_verified */ +#ifdef EXPERIMENTAL_DANE + FALSE,/* dane_verified */ +#endif NULL, /* tls_cipher */ FALSE,/* tls_on_connect */ NULL, /* tls_on_connect_ports */ @@ -116,6 +119,9 @@ tls_support tls_out = { -1, /* tls_active */ 0, /* tls_bits */ FALSE,/* tls_certificate_verified */ +#ifdef EXPERIMENTAL_DANE + FALSE,/* dane_verified */ +#endif NULL, /* tls_cipher */ FALSE,/* tls_on_connect */ NULL, /* tls_on_connect_ports */ diff --git a/src/src/spool_in.c b/src/src/spool_in.c index f53251a86..bbb4da6aa 100644 --- a/src/src/spool_in.c +++ b/src/src/spool_in.c @@ -495,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/tls-openssl.c b/src/src/tls-openssl.c index 79beffadf..2ed1fcbe8 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 #endif +#ifdef EXPERIMENTAL_DANE +# include +#endif + #ifndef DISABLE_OCSP # define EXIM_OCSP_SKEW_SECONDS (300L) @@ -1805,7 +1809,7 @@ if (dane) { dns_record * rr; dns_scan dnss; - uschar * hostnames[2] = { host->name, NULL }; + const char * hostnames[2] = { CS host->name, NULL }; int found = 0; if (DANESSL_init(client_ssl, NULL, hostnames) != 1) -- cgit v1.2.3 From fde080a4f34a8eb0f92af6dfc7e4e6ae01fd61f8 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Sat, 16 Aug 2014 22:36:56 +0100 Subject: Break out dane code to separate functions --- src/src/tls-openssl.c | 188 +++++++++++++++++++++++++++----------------------- 1 file changed, 101 insertions(+), 87 deletions(-) (limited to 'src') diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index 2ed1fcbe8..144be6f63 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -1610,6 +1610,101 @@ else if (verify_check_host(&ob->tls_try_verify_hosts) == OK) 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; + } + } + +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 * *************************************************/ @@ -1649,6 +1744,7 @@ BOOL dane_required; #endif #ifdef EXPERIMENTAL_DANE +tls_out.dane_verified = FALSE; dane_required = verify_check_this_host(&ob->hosts_require_dane, NULL, host->name, host->address, NULL) == OK; @@ -1658,39 +1754,8 @@ if (host->dnssec == DS_YES) || verify_check_this_host(&ob->hosts_try_dane, NULL, host->name, host->address, NULL) == OK ) - { - /* 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 (rc = dns_lookup(&tlsa_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(&tlsa_dnsa)) - { - log_write(0, LOG_MAIN, "DANE error: TLSA lookup not DNSSEC"); - return DEFER; - } - dane = TRUE; - break; - } - } + if ((rc = tlsa_lookup(host, &tlsa_dnsa, dane_required, &dane)) != OK) + return rc; } else if (dane_required) { @@ -1721,9 +1786,6 @@ rc = tls_init(&client_ctx, host, NULL, if (rc != OK) return rc; tls_out.certificate_verified = FALSE; -#ifdef EXPERIMENTAL_DANE -tls_out.dane_verified = FALSE; -#endif client_verify_callback_called = FALSE; if (!expand_check(ob->tls_require_ciphers, US"tls_require_ciphers", @@ -1806,56 +1868,8 @@ if (request_ocsp) #ifdef EXPERIMENTAL_DANE if (dane) - { - dns_record * rr; - dns_scan dnss; - const char * hostnames[2] = { CS host->name, NULL }; - int found = 0; - - if (DANESSL_init(client_ssl, NULL, hostnames) != 1) - return tls_error(US"hostnames load", host, NULL); - - for (rr = dns_next_rr(&tlsa_dnsa, &dnss, RESET_ANSWERS); - rr; - rr = dns_next_rr(&tlsa_dnsa, &dnss, RESET_NEXT) - ) if (rr->type == T_TLSA) - { - uschar * p = rr->data; - int 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(client_ssl, - (uint8_t) usage, (uint8_t) selector, - mdname, p, rr->size - (p - rr->data))) - { - default: - case 0: /* action not taken */ - return tls_error(US"tlsa load", host, NULL); - case 1: break; - } - } - - if (!found) - { - log_write(0, LOG_MAIN, "DANE error: No TLSA records"); - return FAIL; - } - } + if ((rc = dane_tlsa_load(client_ssl, host, &tlsa_dnsa)) != OK) + return rc; #endif @@ -1869,7 +1883,7 @@ alarm(0); #ifdef EXPERIMENTAL_DANE if (dane) - DANESSL_cleanup(client_ssl); /*XXX earliest possible callpoint. Too early? */ + DANESSL_cleanup(client_ssl); #endif if (rc <= 0) -- cgit v1.2.3 From 594706ea2e56fe8c972eab772bd3e58c7a0c89ab Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Sun, 17 Aug 2014 00:41:17 +0100 Subject: Add observability variables and provision for avoiding OCSP conflicts --- doc/doc-txt/experimental-spec.txt | 25 ++++++++++++++++++++++++- src/src/expand.c | 6 ++++++ src/src/globals.c | 2 ++ src/src/globals.h | 1 + src/src/tls-openssl.c | 32 +++++++++++++++++++++++++------- test/confs/5840 | 3 +++ 6 files changed, 61 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/doc/doc-txt/experimental-spec.txt b/doc/doc-txt/experimental-spec.txt index b1b89e007..c060a6c5a 100644 --- a/doc/doc-txt/experimental-spec.txt +++ b/doc/doc-txt/experimental-spec.txt @@ -1235,7 +1235,25 @@ must have a correct name (SubjectName or SubjectAltName). The use of OCSP-stapling should be considered, allowing for fast revocation of certificates (which would otherwise be limited by the DNS TTL on the TLSA records). However, -this is likely to only be usable with DANE_TA. +this is likely to only be usable with DANE_TA. NOTE: the +default is to request OCSP for all hosts; the certificate +chain in DANE_EE usage will be insufficient to validate +the OCSP proof and verification will fail. Either disable +OCSP completely or use the (new) variable $tls_out_tlsa_usage +like so: + + hosts_request_ocsp = ${if or { {= {4}{$tls_out_tlsa_usage}} \ + {= {0}{$tls_out_tlsa_usage}} } \ + {*}{}} +The variable is a bitfield with numbered bits set for TLSA +record usage codes. The zero above means DANE was not in use, +the four means that only DANE_TA usage TLSA records were +found. If the definition of hosts_require_ocsp or +hosts_request_ocsp includes the string "tls_out_tlsa_usage", +they are re-expanded in time to control the OCSP request. + +[ All a bit complicated. Should we make that definition +the default? Should we override the user's definition? ] For client-side DANE there are two new smtp transport options, @@ -1260,6 +1278,11 @@ and dnssec_require_domains is ignored. If verification was successful using DANE then the "CV" item in the delivery log line will show as "CV=dane". +There is a new variable $tls_out_dane which will have "yes" if +verification succeeded using DANE and "no" otherwise (only useful +in combination with EXPERIMENTAL_TPDA), and a new variable +$tls_out_tlsa_usage (detailed above). + -------------------------------------------------------------- End of file diff --git a/src/src/expand.c b/src/src/expand.c index e5af63d89..ba2c6f7cd 100644 --- a/src/src/expand.c +++ b/src/src/expand.c @@ -684,6 +684,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 }, @@ -691,6 +694,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 d09903d65..409c324e9 100644 --- a/src/src/globals.c +++ b/src/src/globals.c @@ -105,6 +105,7 @@ tls_support tls_in = { FALSE,/* tls_certificate_verified */ #ifdef EXPERIMENTAL_DANE FALSE,/* dane_verified */ + 0, /* tlsa_usage */ #endif NULL, /* tls_cipher */ FALSE,/* tls_on_connect */ @@ -121,6 +122,7 @@ tls_support tls_out = { FALSE,/* tls_certificate_verified */ #ifdef EXPERIMENTAL_DANE FALSE,/* dane_verified */ + 0, /* tlsa_usage */ #endif NULL, /* tls_cipher */ FALSE,/* tls_on_connect */ diff --git a/src/src/globals.h b/src/src/globals.h index 654114848..1adda6411 100644 --- a/src/src/globals.h +++ b/src/src/globals.h @@ -84,6 +84,7 @@ typedef struct { 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 */ diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index 144be6f63..57b0808fb 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -1693,6 +1693,8 @@ for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); return tls_error(US"tlsa load", host, NULL); case 1: break; } + + tls_out.tlsa_usage |= 1<hosts_require_dane, NULL, host->name, host->address, NULL) == OK; @@ -1764,7 +1767,6 @@ else if (dane_required) log_write(0, LOG_MAIN, "DANE error: previous lookup not DNSSEC"); return FAIL; } - #endif #ifndef DISABLE_OCSP @@ -1855,9 +1857,31 @@ 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()) */ +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; + } + } if (request_ocsp) { SSL_set_tlsext_status_type(client_ssl, TLSEXT_STATUSTYPE_ocsp); @@ -1866,12 +1890,6 @@ if (request_ocsp) } #endif -#ifdef EXPERIMENTAL_DANE -if (dane) - if ((rc = dane_tlsa_load(client_ssl, host, &tlsa_dnsa)) != OK) - return rc; -#endif - /* There doesn't seem to be a built-in timeout on connection. */ diff --git a/test/confs/5840 b/test/confs/5840 index c381ef616..4359b9a59 100644 --- a/test/confs/5840 +++ b/test/confs/5840 @@ -67,6 +67,9 @@ send_to_server: # hosts_try_dane = * hosts_require_dane = * + hosts_request_ocsp = ${if or { {= {4}{$tls_out_tlsa_usage}} \ + {= {0}{$tls_out_tlsa_usage}} } \ + {*}{}} # ----- Retry ----- -- cgit v1.2.3 From b50c8b8487f906a7e18580e9020783afde09d9f7 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Sun, 17 Aug 2014 14:42:43 +0100 Subject: Feature compile-guard --- src/src/tls-openssl.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src') diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index 57b0808fb..de2e7a3bd 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -1866,6 +1866,7 @@ if (dane) #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; @@ -1882,6 +1883,8 @@ if (request_ocsp) NULL, host->name, host->address, NULL) == OK; } } +# endif + if (request_ocsp) { SSL_set_tlsext_status_type(client_ssl, TLSEXT_STATUSTYPE_ocsp); -- cgit v1.2.3 From fca41d5a245023376c7d7716a3f84abc2aaa4b8e Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Sun, 17 Aug 2014 16:38:32 +0100 Subject: Override an unchanged default hosts_request_ocsp when DANE is used --- doc/doc-txt/experimental-spec.txt | 23 +++++++++++------------ src/src/tls-openssl.c | 27 ++++++++++++++++++++++----- src/src/transports/smtp.c | 2 +- 3 files changed, 34 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/doc/doc-txt/experimental-spec.txt b/doc/doc-txt/experimental-spec.txt index c060a6c5a..80e970cc1 100644 --- a/doc/doc-txt/experimental-spec.txt +++ b/doc/doc-txt/experimental-spec.txt @@ -1236,24 +1236,23 @@ The use of OCSP-stapling should be considered, allowing for fast revocation of certificates (which would otherwise be limited by the DNS TTL on the TLSA records). However, this is likely to only be usable with DANE_TA. NOTE: the -default is to request OCSP for all hosts; the certificate -chain in DANE_EE usage will be insufficient to validate -the OCSP proof and verification will fail. Either disable -OCSP completely or use the (new) variable $tls_out_tlsa_usage -like so: - - hosts_request_ocsp = ${if or { {= {4}{$tls_out_tlsa_usage}} \ - {= {0}{$tls_out_tlsa_usage}} } \ +default of requesting OCSP for all hosts is modified iff +DANE is in use, to: + + hosts_request_ocsp = ${if or { {= {0}{$tls_out_tlsa_usage}} \ + {= {4}{$tls_out_tlsa_usage}} } \ {*}{}} -The variable is a bitfield with numbered bits set for TLSA -record usage codes. The zero above means DANE was not in use, + +The (new) variable $tls_out_tlsa_usage is a bitfield with +numbered bits set for TLSA record usage codes. +The zero above means DANE was not in use, the four means that only DANE_TA usage TLSA records were found. If the definition of hosts_require_ocsp or hosts_request_ocsp includes the string "tls_out_tlsa_usage", they are re-expanded in time to control the OCSP request. -[ All a bit complicated. Should we make that definition -the default? Should we override the user's definition? ] +This modification of hosts_request_ocsp is only done if +it has the default value of "*". For client-side DANE there are two new smtp transport options, diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index de2e7a3bd..343122615 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -1771,11 +1771,28 @@ else if (dane_required) #ifndef DISABLE_OCSP { - 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; + 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 diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index 9abc69d51..1865adee8 100644 --- a/src/src/transports/smtp.c +++ b/src/src/transports/smtp.c @@ -216,7 +216,7 @@ smtp_transport_options_block smtp_transport_option_defaults = { 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 */ -- cgit v1.2.3 From e8e86723959ffd4ab6d6b320538ec0e86f828798 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Sun, 17 Aug 2014 18:26:08 +0100 Subject: Unbreak utf8clean testcase. Broken by my compile quitening; the issue was a variable declared local in a loop body and used for carrying data from one iteration to the next. I'd blindly added an initialiser, destroying the data. However, I *think* that compilers might be at liberty to not use the same location for separate iterations; if so the code was broken (and only worked by chance). Fix by moving the declaration outside the loop. --- src/src/expand.c | 10 ++++------ test/confs/0600 | 3 ++- 2 files changed, 6 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/src/expand.c b/src/src/expand.c index ba2c6f7cd..b0e76ba27 100644 --- a/src/src/expand.c +++ b/src/src/expand.c @@ -6376,16 +6376,14 @@ while (*s != 0) { int seq_len = 0, index = 0; int bytes_left = 0; + long codepoint = -1; uschar seq_buff[4]; /* accumulate utf-8 here */ while (*sub != 0) { - int complete; - long codepoint = 0; - uschar c; + int complete = 0; + uschar c = *sub++; - complete = 0; - c = *sub++; if (bytes_left) { if ((c & 0xc0) != 0x80) @@ -6400,7 +6398,7 @@ while (*s != 0) if (--bytes_left == 0) /* codepoint complete */ { if(codepoint > 0x10FFFF) /* is it too large? */ - complete = -1; /* error */ + complete = -1; /* error (RFC3629 limit) */ else { /* finished; output utf-8 sequence */ yield = string_cat(yield, &size, &ptr, seq_buff, seq_len); diff --git a/test/confs/0600 b/test/confs/0600 index 0347e4c60..61a9c0801 100644 --- a/test/confs/0600 +++ b/test/confs/0600 @@ -1,4 +1,5 @@ -# Exim test configuration 0005 +# Exim test configuration 0600 +# utf8clean:string exim_path = EXIM_PATH host_lookup_order = bydns -- cgit v1.2.3 From 83b2729321db62e758a300e372b2dd74e527d004 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Wed, 20 Aug 2014 20:34:17 +0100 Subject: Merge branch dane-tpda into dane Conflicts: doc/doc-txt/experimental-spec.txt src/src/deliver.c src/src/functions.h src/src/smtp_out.c src/src/tls-openssl.c src/src/transports/smtp.c src/src/verify.c --- src/src/deliver.c | 6 ++ src/src/tls-openssl.c | 26 ++++++++ src/src/verify.c | 8 ++- test/confs/5860 | 88 ++++++++++++++++++++++++++++ test/log/5860 | 40 +++++++++++++ test/scripts/5860-DANE-OpenSSL-TPDA/5860 | 30 ++++++++++ test/scripts/5860-DANE-OpenSSL-TPDA/REQUIRES | 4 ++ 7 files changed, 200 insertions(+), 2 deletions(-) create mode 100644 test/confs/5860 create mode 100644 test/log/5860 create mode 100644 test/scripts/5860-DANE-OpenSSL-TPDA/5860 create mode 100644 test/scripts/5860-DANE-OpenSSL-TPDA/REQUIRES (limited to 'src') diff --git a/src/src/deliver.c b/src/src/deliver.c index d00af9c11..676de556d 100644 --- a/src/src/deliver.c +++ b/src/src/deliver.c @@ -1134,6 +1134,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); @@ -1152,6 +1155,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 } diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index 735ebff06..2e95a467a 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -437,6 +437,9 @@ 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)); @@ -444,6 +447,25 @@ 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; @@ -1958,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"); diff --git a/src/src/verify.c b/src/src/verify.c index edd9ad17d..d2ecb9cde 100644 --- a/src/src/verify.c +++ b/src/src/verify.c @@ -660,7 +660,7 @@ else /* TLS negotiation failed; give an error. Try in clear on a new connection, if the options permit it for this host. */ if (rc != OK) - { + { if ( rc == DEFER && ob->tls_tempfail_tryclear && !smtps @@ -672,7 +672,11 @@ else #endif ) { - (void)close(inblock.sock); + (void)close(inblock.sock); +#ifdef EXPERIMENTAL_TPDA + (void) tpda_raise_event(addr->transport->tpda_event_action, + US"tcp:close", NULL); +#endif log_write(0, LOG_MAIN, "TLS session failure: delivering unencrypted " "to %s [%s] (not in hosts_require_tls)", host->name, host->address); suppress_tls = TRUE; diff --git a/test/confs/5860 b/test/confs/5860 new file mode 100644 index 000000000..7dc4b0952 --- /dev/null +++ b/test/confs/5860 @@ -0,0 +1,88 @@ +# Exim test configuration 5850 +# DANE + +SERVER= + +exim_path = EXIM_PATH +host_lookup_order = bydns +primary_hostname = myhost.test.ex +rfc1413_query_timeout = 0s +spool_directory = DIR/spool +log_file_path = DIR/spool/log/SERVER%slog +gecos_pattern = "" +gecos_name = CALLER_NAME + +# ----- Main settings ----- + +acl_smtp_rcpt = accept + +log_selector = +received_recipients +tls_peerdn +tls_certificate_verified + +queue_only +queue_run_in_order + +tls_advertise_hosts = * + +# Set certificate only if server +CDIR1 = DIR/aux-fixed +CDIR2 = DIR/aux-fixed/exim-ca/example.com/server1.example.com + +tls_certificate = ${if eq {SERVER}{server} \ + {${if eq {DETAILS}{ta} \ + {CDIR2/fullchain.pem}\ + {CDIR1/cert1}}}\ + fail} + +tls_privatekey = ${if eq {SERVER}{server} \ + {${if eq {DETAILS}{ta} \ + {CDIR2/server1.example.com.unlocked.key}\ + {CDIR1/cert1}}}\ + fail} + + +begin acl + +logger: + accept condition = ${if eq {tls} {${listextract{1}{$tpda_event}}}} + logwrite = $tpda_event depth = $tpda_data \ + <${certextract {subject} {$tls_out_peercert}}> +# message = noooo + + accept condition = ${if eq {msg} {${listextract{1}{$tpda_event}}}} + logwrite = $tpda_event dane=$tls_out_dane + accept + +# ----- Routers ----- + +begin routers + +client: + driver = dnslookup + condition = ${if eq {SERVER}{}} + dnssec_request_domains = * + self = send + transport = send_to_server + +server: + driver = redirect + data = :blackhole: + + +# ----- Transports ----- + +begin transports + +send_to_server: + driver = smtp + allow_localhost + port = PORT_D + +# hosts_try_dane = * + hosts_require_dane = * + hosts_request_ocsp = ${if or { {= {4}{$tls_out_tlsa_usage}} \ + {= {0}{$tls_out_tlsa_usage}} } \ + {*}{}} + + tpda_event_action = ${acl {logger}} + +# End diff --git a/test/log/5860 b/test/log/5860 new file mode 100644 index 000000000..7c1bf6657 --- /dev/null +++ b/test/log/5860 @@ -0,0 +1,40 @@ +1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss for CALLER@dane256ee.test.ex +1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss for CALLER@mxdane512ee.test.ex +1999-03-02 09:44:33 Start queue run: pid=pppp -qf +1999-03-02 09:44:33 10HmaX-0005vi-00 tls:cert depth = 0 +1999-03-02 09:44:33 10HmaX-0005vi-00 => CALLER@dane256ee.test.ex R=client T=send_to_server H=dane256ee.test.ex [ip4.ip4.ip4.ip4] X=TLSv1:AES256-SHA:256 CV=dane DN="/C=UK/O=The Exim Maintainers/OU=Test Suite/CN=Phil Pennock" C="250 OK id=10HmaZ-0005vi-00" +1999-03-02 09:44:33 10HmaX-0005vi-00 msg:delivery dane=yes +1999-03-02 09:44:33 10HmaX-0005vi-00 Completed +1999-03-02 09:44:33 10HmaY-0005vi-00 tls:cert depth = 0 +1999-03-02 09:44:33 10HmaY-0005vi-00 => CALLER@mxdane512ee.test.ex R=client T=send_to_server H=dane512ee.test.ex [ip4.ip4.ip4.ip4] X=TLSv1:AES256-SHA:256 CV=dane DN="/C=UK/O=The Exim Maintainers/OU=Test Suite/CN=Phil Pennock" C="250 OK id=10HmbA-0005vi-00" +1999-03-02 09:44:33 10HmaY-0005vi-00 msg:delivery dane=yes +1999-03-02 09:44:33 10HmaY-0005vi-00 Completed +1999-03-02 09:44:33 End queue run: pid=pppp -qf +1999-03-02 09:44:33 10HmbB-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss for CALLER@mxdane256ta.test.ex +1999-03-02 09:44:33 Start queue run: pid=pppp -qf +1999-03-02 09:44:33 10HmbB-0005vi-00 tls:cert depth = 2 +1999-03-02 09:44:33 10HmbB-0005vi-00 tls:cert depth = 0 +1999-03-02 09:44:33 10HmbB-0005vi-00 tls:cert depth = 2 +1999-03-02 09:44:33 10HmbB-0005vi-00 tls:cert depth = 1 +1999-03-02 09:44:33 10HmbB-0005vi-00 tls:cert depth = 0 +1999-03-02 09:44:33 10HmbB-0005vi-00 => CALLER@mxdane256ta.test.ex R=client T=send_to_server H=dane256ta.test.ex [ip4.ip4.ip4.ip4] X=TLSv1:AES256-SHA:256 CV=dane DN="/CN=server1.example.com" C="250 OK id=10HmbC-0005vi-00" +1999-03-02 09:44:33 10HmbB-0005vi-00 msg:delivery dane=yes +1999-03-02 09:44:33 10HmbB-0005vi-00 Completed +1999-03-02 09:44:33 End queue run: pid=pppp -qf + +******** SERVER ******** +1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225 +1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@myhost.test.ex H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtps X=TLSv1:AES256-SHA:256 CV=no S=sss id=E10HmaX-0005vi-00@myhost.test.ex for CALLER@dane256ee.test.ex +1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@myhost.test.ex H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtps X=TLSv1:AES256-SHA:256 CV=no S=sss id=E10HmaY-0005vi-00@myhost.test.ex for CALLER@mxdane512ee.test.ex +1999-03-02 09:44:33 Start queue run: pid=pppp -qf +1999-03-02 09:44:33 10HmaZ-0005vi-00 => :blackhole: R=server +1999-03-02 09:44:33 10HmaZ-0005vi-00 Completed +1999-03-02 09:44:33 10HmbA-0005vi-00 => :blackhole: R=server +1999-03-02 09:44:33 10HmbA-0005vi-00 Completed +1999-03-02 09:44:33 End queue run: pid=pppp -qf +1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225 +1999-03-02 09:44:33 10HmbC-0005vi-00 <= CALLER@myhost.test.ex H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtps X=TLSv1:AES256-SHA:256 CV=no S=sss id=E10HmbB-0005vi-00@myhost.test.ex for CALLER@mxdane256ta.test.ex +1999-03-02 09:44:33 Start queue run: pid=pppp -qf +1999-03-02 09:44:33 10HmbC-0005vi-00 => :blackhole: R=server +1999-03-02 09:44:33 10HmbC-0005vi-00 Completed +1999-03-02 09:44:33 End queue run: pid=pppp -qf diff --git a/test/scripts/5860-DANE-OpenSSL-TPDA/5860 b/test/scripts/5860-DANE-OpenSSL-TPDA/5860 new file mode 100644 index 000000000..94bc4d2b8 --- /dev/null +++ b/test/scripts/5860-DANE-OpenSSL-TPDA/5860 @@ -0,0 +1,30 @@ +# DANE client: TPDA +# +exim -DSERVER=server -DDETAILS=ee -bd -oX PORT_D +**** +# TLSA (3 1 1) +exim CALLER@dane256ee.test.ex +Testing +**** +# TLSA (3 1 2) +exim CALLER@mxdane512ee.test.ex +Testing +**** +exim -qf +**** +killdaemon +exim -DSERVER=server -DDETAILS=ee -DNOTDAEMON -qf +**** +# +# +exim -DSERVER=server -DDETAILS=ta -bd -oX PORT_D +**** +# TLSA (2 0 1) +exim CALLER@mxdane256ta.test.ex +Testing +**** +exim -qf +**** +killdaemon +exim -DSERVER=server -DDETAILS=ta -DNOTDAEMON -qf +**** diff --git a/test/scripts/5860-DANE-OpenSSL-TPDA/REQUIRES b/test/scripts/5860-DANE-OpenSSL-TPDA/REQUIRES new file mode 100644 index 000000000..7e51b4fa7 --- /dev/null +++ b/test/scripts/5860-DANE-OpenSSL-TPDA/REQUIRES @@ -0,0 +1,4 @@ +support Experimental_DANE +support Experimental_TPDA +support OpenSSL +running IPv4 -- cgit v1.2.3 From 5c1da879a397c91b649598fff205d0fd7906eff1 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Sun, 31 Aug 2014 21:54:58 +0100 Subject: Update comment --- src/src/globals.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/src/globals.c b/src/src/globals.c index f5ed80fa2..8407e6570 100644 --- a/src/src/globals.c +++ b/src/src/globals.c @@ -1395,7 +1395,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 }; -- cgit v1.2.3