summaryrefslogtreecommitdiffstats
path: root/src/tls/tls_dane.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/tls/tls_dane.c2080
1 files changed, 2080 insertions, 0 deletions
diff --git a/src/tls/tls_dane.c b/src/tls/tls_dane.c
new file mode 100644
index 0000000..3375905
--- /dev/null
+++ b/src/tls/tls_dane.c
@@ -0,0 +1,2080 @@
+/*++
+/* NAME
+/* tls_dane 3
+/* SUMMARY
+/* Support for RFC 6698, 7671, 7672 (DANE) certificate matching
+/* SYNOPSIS
+/* #include <tls.h>
+/*
+/* int tls_dane_avail()
+/*
+/* void tls_dane_flush()
+/*
+/* void tls_dane_verbose(on)
+/* int on;
+/*
+/* TLS_DANE *tls_dane_alloc()
+/*
+/* void tls_dane_free(dane)
+/* TLS_DANE *dane;
+/*
+/* void tls_dane_add_ee_digests(dane, mdalg, digest, delim)
+/* TLS_DANE *dane;
+/* const char *mdalg;
+/* const char *digest;
+/* const char *delim;
+/*
+/* int tls_dane_load_trustfile(dane, tafile)
+/* TLS_DANE *dane;
+/* const char *tafile;
+/*
+/* int tls_dane_match(TLSContext, usage, cert, depth)
+/* TLS_SESS_STATE *TLScontext;
+/* int usage;
+/* X509 *cert;
+/* int depth;
+/*
+/* void tls_dane_set_callback(ssl_ctx, TLScontext)
+/* SSL_CTX *ssl_ctx;
+/* TLS_SESS_STATE *TLScontext;
+/*
+/* TLS_DANE *tls_dane_resolve(port, proto, hostrr, forcetlsa)
+/* unsigned port;
+/* const char *proto;
+/* DNS_RR *hostrr;
+/* int forcetlsa;
+/*
+/* int tls_dane_unusable(dane)
+/* const TLS_DANE *dane;
+/*
+/* int tls_dane_notfound(dane)
+/* const TLS_DANE *dane;
+/* DESCRIPTION
+/* tls_dane_avail() returns true if the features required to support DANE
+/* are present in OpenSSL's libcrypto and in libresolv. Since OpenSSL's
+/* libcrypto is not initialized until we call tls_client_init(), calls
+/* to tls_dane_avail() must be deferred until this initialization is
+/* completed successufully.
+/*
+/* tls_dane_flush() flushes all entries from the cache, and deletes
+/* the cache.
+/*
+/* tls_dane_verbose() turns on verbose logging of TLSA record lookups.
+/*
+/* tls_dane_alloc() returns a pointer to a newly allocated TLS_DANE
+/* structure with null ta and ee digest sublists.
+/*
+/* tls_dane_free() frees the structure allocated by tls_dane_alloc().
+/*
+/* tls_dane_add_ee_digests() splits "digest" using the characters in
+/* "delim" as delimiters and stores the results on the EE match list
+/* to match either a certificate or a public key. This is an incremental
+/* interface, that builds a TLS_DANE structure outside the cache by
+/* manually adding entries.
+/*
+/* tls_dane_load_trustfile() imports trust-anchor certificates and
+/* public keys from a file (rather than DNS TLSA records).
+/*
+/* tls_dane_match() matches the full and/or public key digest of
+/* "cert" against each candidate digest in TLScontext->dane. If usage
+/* is TLS_DANE_EE, the match is against end-entity digests, otherwise
+/* it is against trust-anchor digests. Returns true if a match is found,
+/* false otherwise.
+/*
+/* tls_dane_set_callback() wraps the SSL certificate verification logic
+/* in a function that modifies the input trust chain and trusted
+/* certificate store to map DANE TA validation onto the existing PKI
+/* verification model. When TLScontext is NULL the callback is
+/* cleared, otherwise it is set. This callback should only be set
+/* when out-of-band trust-anchors (via DNSSEC DANE TLSA records or
+/* per-destination local configuration) are provided. Such trust
+/* anchors always override the legacy public CA PKI. Otherwise, the
+/* callback MUST be cleared.
+/*
+/* tls_dane_resolve() maps a (port, protocol, hostrr) tuple to a
+/* corresponding TLS_DANE policy structure found in the DNS. The port
+/* argument is in network byte order. A null pointer is returned when
+/* the DNS query for the TLSA record tempfailed. In all other cases the
+/* return value is a pointer to the corresponding TLS_DANE structure.
+/* The caller must free the structure via tls_dane_free().
+/*
+/* tls_dane_unusable() checks whether a cached TLS_DANE record is
+/* the result of a validated RRset, with no usable elements. In
+/* this case, TLS is mandatory, but certificate verification is
+/* not DANE-based.
+/*
+/* tls_dane_notfound() checks whether a cached TLS_DANE record is
+/* the result of a validated DNS lookup returning NODATA. In
+/* this case, TLS is not required by RFC, though users may elect
+/* a mandatory TLS fallback policy.
+/*
+/* Arguments:
+/* .IP dane
+/* Pointer to a TLS_DANE structure that lists the valid trust-anchor
+/* and end-entity full-certificate and/or public-key digests.
+/* .IP port
+/* The TCP port in network byte order.
+/* .IP proto
+/* Almost certainly "tcp".
+/* .IP hostrr
+/* DNS_RR pointer to TLSA base domain data.
+/* .IP forcetlsa
+/* When true, TLSA lookups are performed even when the qname and rname
+/* are insecure. This is only useful in the unlikely case that DLV is
+/* used to secure the TLSA RRset in an otherwise insecure zone.
+/* .IP TLScontext
+/* Client context with TA/EE matching data and related state.
+/* .IP usage
+/* Trust anchor (TLS_DANE_TA) or end-entity (TLS_DANE_EE) digests?
+/* .IP cert
+/* Certificate from peer trust chain (CA or leaf server).
+/* .IP depth
+/* The certificate depth for logging.
+/* .IP ssl_ctx
+/* The global SSL_CTX structure used to initialize child SSL
+/* conenctions.
+/* .IP mdalg
+/* Name of a message digest algorithm suitable for computing secure
+/* (1st pre-image resistant) message digests of certificates. For now,
+/* md5, sha1, or member of SHA-2 family if supported by OpenSSL.
+/* .IP digest
+/* The digest (or list of digests concatenated with characters from
+/* "delim") to be added to the TLS_DANE record.
+/* .IP delim
+/* The set of delimiter characters used above.
+/* LICENSE
+/* .ad
+/* .fi
+/* This software is free. You can do with it whatever you want.
+/* The original author kindly requests that you acknowledge
+/* the use of his software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*
+/* Viktor Dukhovni
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <ctype.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+#ifdef USE_TLS
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <vstring.h>
+#include <events.h> /* event_time() */
+#include <timecmp.h>
+#include <ctable.h>
+#include <hex_code.h>
+#include <safe_ultostr.h>
+#include <split_at.h>
+#include <name_code.h>
+
+#define STR(x) vstring_str(x)
+
+/* Global library */
+
+#include <mail_params.h>
+
+/* DNS library. */
+
+#include <dns.h>
+
+/* TLS library. */
+
+#define TLS_INTERNAL
+#include <tls.h>
+
+/* Application-specific. */
+
+#undef DANE_TLSA_SUPPORT
+
+#if defined(TLSEXT_MAXLEN_host_name) && RES_USE_DNSSEC && RES_USE_EDNS0
+#define DANE_TLSA_SUPPORT
+static int dane_tlsa_support = 1;
+
+#else
+static int dane_tlsa_support = 0;
+
+#endif
+
+static const char *signalg;
+static ASN1_OBJECT *serverAuth;
+
+/*
+ * https://www.iana.org/assignments/dane-parameters/dane-parameters.xhtml
+ */
+typedef struct {
+ const char *mdalg;
+ uint8_t dane_id;
+} iana_digest;
+
+static iana_digest iana_table[] = {
+ {"", DNS_TLSA_MATCHING_TYPE_NO_HASH_USED},
+ {"sha256", DNS_TLSA_MATCHING_TYPE_SHA256},
+ {"sha512", DNS_TLSA_MATCHING_TYPE_SHA512},
+ {0, 0}
+};
+
+typedef struct dane_digest {
+ struct dane_digest *next; /* linkage */
+ const char *mdalg; /* OpenSSL name */
+ const EVP_MD *md; /* OpenSSL EVP handle */
+ int len; /* digest octet length */
+ int pref; /* tls_dane_digests index or -1 */
+ uint8_t dane_id; /* IANA id */
+} dane_digest;
+
+#define MAXDIGESTS 256 /* RFC limit */
+static dane_digest *digest_list;
+
+/*
+ * This is not intended to be a long-term cache of pre-parsed TLSA data,
+ * rather we primarily want to avoid fetching and parsing the TLSA records
+ * for a single multi-homed MX host more than once per delivery. Therefore,
+ * we keep the table reasonably small.
+ */
+#define CACHE_SIZE 20
+static CTABLE *dane_cache;
+
+static int dane_initialized;
+static int dane_verbose;
+
+/* tls_dane_verbose - enable/disable verbose logging */
+
+void tls_dane_verbose(int on)
+{
+ dane_verbose = on;
+}
+
+/* add_digest - validate and append digest to digest list */
+
+static dane_digest *add_digest(char *mdalg, int pref)
+{
+ iana_digest *i;
+ dane_digest *d;
+ int dane_id = -1;
+ const char *dane_mdalg = mdalg;
+ char *value = split_at(mdalg, '=');
+ const EVP_MD *md = 0;
+ size_t mdlen = 0;
+
+ if (value && *value) {
+ unsigned long l;
+ char *endcp;
+
+ /*
+ * XXX: safe_strtoul() does not flag empty or white-space only input.
+ * Since we get idbuf by splitting white-space/comma delimited
+ * tokens, this is not a problem here. Fixed as of 210131209.
+ */
+ l = safe_strtoul(value, &endcp, 10);
+ if ((l == 0 && (errno == EINVAL || endcp == value))
+ || l >= MAXDIGESTS
+ || *endcp) {
+ msg_warn("Invalid matching type number in %s: %s=%s",
+ VAR_TLS_DANE_DIGESTS, mdalg, value);
+ return (0);
+ }
+ dane_id = l;
+ }
+
+ /*
+ * Check for known IANA conflicts
+ */
+ for (i = iana_table; i->mdalg; ++i) {
+ if (*mdalg && strcasecmp(i->mdalg, mdalg) == 0) {
+ if (dane_id >= 0 && i->dane_id != dane_id) {
+ msg_warn("Non-standard value in %s: %s%s%s",
+ VAR_TLS_DANE_DIGESTS, mdalg,
+ value ? "=" : "", value ? value : "");
+ return (0);
+ }
+ dane_id = i->dane_id;
+ } else if (i->dane_id == dane_id) {
+ if (*mdalg) {
+ msg_warn("Non-standard algorithm in %s: %s%s%s",
+ VAR_TLS_DANE_DIGESTS, mdalg,
+ value ? "=" : "", value ? value : "");
+ return (0);
+ }
+ dane_mdalg = i->mdalg;
+ }
+ }
+
+ /*
+ * Check for unknown implicit digest or value
+ */
+ if (dane_id < 0 || (dane_id > 0 && !*dane_mdalg)) {
+ msg_warn("Unknown incompletely specified element in %s: %s%s%s",
+ VAR_TLS_DANE_DIGESTS, mdalg,
+ value ? "=" : "", value ? value : "");
+ return 0;
+ }
+
+ /*
+ * Check for duplicate entries
+ */
+ for (d = digest_list; d; d = d->next) {
+ if (strcasecmp(d->mdalg, dane_mdalg) == 0
+ || d->dane_id == dane_id) {
+ msg_warn("Duplicate element in %s: %s%s%s",
+ VAR_TLS_DANE_DIGESTS, mdalg,
+ value ? "=" : "", value ? value : "");
+ return (0);
+ }
+ }
+
+ if (*dane_mdalg
+ && ((md = tls_digest_byname(dane_mdalg, NULL)) == 0
+ || (mdlen = EVP_MD_size(md)) <= 0
+ || mdlen > EVP_MAX_MD_SIZE)) {
+ msg_warn("Unimplemented digest algorithm in %s: %s%s%s",
+ VAR_TLS_DANE_DIGESTS, mdalg,
+ value ? "=" : "", value ? value : "");
+ return (0);
+ }
+ d = (dane_digest *) mymalloc(sizeof(*d));
+ d->next = digest_list;
+ d->mdalg = mystrdup(dane_mdalg);
+ d->md = md;
+ d->len = mdlen;
+ d->pref = pref;
+ d->dane_id = dane_id;
+
+ return (digest_list = d);
+}
+
+/* digest_byid - locate digest_table entry for given IANA id */
+
+static dane_digest *digest_byid(uint8_t dane_id)
+{
+ dane_digest *d;
+
+ for (d = digest_list; d; d = d->next)
+ if (d->dane_id == dane_id)
+ return (d);
+ return (0);
+}
+
+/* digest_pref_byid - digest preference by IANA id */
+
+static int digest_pref_byid(uint8_t dane_id)
+{
+ dane_digest *d = digest_byid(dane_id);
+
+ return (d ? (d->pref) : (MAXDIGESTS + dane_id));
+}
+
+/* dane_init - initialize DANE parameters */
+
+static void dane_init(void)
+{
+ int digest_pref = 0;
+ char *cp;
+ char *save;
+ char *tok;
+ static char fullmtype[] = "=0";
+ dane_digest *d;
+
+ /*
+ * Add the full matching type at highest preference and then the users
+ * configured list.
+ *
+ * The most preferred digest will be used for hashing full values for
+ * comparison.
+ */
+ if (add_digest(fullmtype, 0)) {
+ save = cp = mystrdup(var_tls_dane_digests);
+ while ((tok = mystrtok(&cp, CHARS_COMMA_SP)) != 0) {
+ if ((d = add_digest(tok, ++digest_pref)) == 0) {
+ signalg = 0;
+ break;
+ }
+ if (digest_pref == 1) {
+ signalg = d->mdalg;
+ }
+ }
+ myfree(save);
+ }
+ /* Don't report old news */
+ ERR_clear_error();
+
+ /*
+ * DANE TLSA support requires working DANE digests.
+ */
+ if ((serverAuth = OBJ_nid2obj(NID_server_auth)) == 0) {
+ msg_warn("cannot designate intermediate TA certificates, "
+ "no DANE support");
+ tls_print_errors();
+ dane_tlsa_support = 0;
+ } else if (signalg == 0) {
+ msg_warn("digest algorithm initializaton failed, no DANE support");
+ tls_print_errors();
+ dane_tlsa_support = 0;
+ }
+ dane_initialized = 1;
+}
+
+/* tls_dane_avail - check for availability of dane required digests */
+
+int tls_dane_avail(void)
+{
+ if (!dane_initialized)
+ dane_init();
+ return (dane_tlsa_support);
+}
+
+/* tls_dane_flush - flush the cache */
+
+void tls_dane_flush(void)
+{
+ if (dane_cache)
+ ctable_free(dane_cache);
+ dane_cache = 0;
+}
+
+/* tls_dane_alloc - allocate a TLS_DANE structure */
+
+TLS_DANE *tls_dane_alloc(void)
+{
+ TLS_DANE *dane = (TLS_DANE *) mymalloc(sizeof(*dane));
+
+ dane->ta = 0;
+ dane->ee = 0;
+ dane->certs = 0;
+ dane->pkeys = 0;
+ dane->base_domain = 0;
+ dane->flags = 0;
+ dane->expires = 0;
+ dane->refs = 1;
+ return (dane);
+}
+
+static void ta_cert_insert(TLS_DANE *d, X509 *x)
+{
+ TLS_CERTS *new = (TLS_CERTS *) mymalloc(sizeof(*new));
+
+ X509_up_ref(x);
+ new->cert = x;
+ new->next = d->certs;
+ d->certs = new;
+}
+
+static void free_ta_certs(TLS_DANE *d)
+{
+ TLS_CERTS *head;
+ TLS_CERTS *next;
+
+ for (head = d->certs; head; head = next) {
+ next = head->next;
+ X509_free(head->cert);
+ myfree((void *) head);
+ }
+}
+
+static void ta_pkey_insert(TLS_DANE *d, EVP_PKEY *k)
+{
+ TLS_PKEYS *new = (TLS_PKEYS *) mymalloc(sizeof(*new));
+
+ EVP_PKEY_up_ref(k);
+ new->pkey = k;
+ new->next = d->pkeys;
+ d->pkeys = new;
+}
+
+static void free_ta_pkeys(TLS_DANE *d)
+{
+ TLS_PKEYS *head;
+ TLS_PKEYS *next;
+
+ for (head = d->pkeys; head; head = next) {
+ next = head->next;
+ EVP_PKEY_free(head->pkey);
+ myfree((void *) head);
+ }
+}
+
+static void tlsa_free(TLS_TLSA *tlsa)
+{
+
+ myfree(tlsa->mdalg);
+ if (tlsa->certs)
+ argv_free(tlsa->certs);
+ if (tlsa->pkeys)
+ argv_free(tlsa->pkeys);
+ myfree((void *) tlsa);
+}
+
+/* tls_dane_free - free a TLS_DANE structure */
+
+void tls_dane_free(TLS_DANE *dane)
+{
+ TLS_TLSA *tlsa;
+ TLS_TLSA *next;
+
+ if (--dane->refs > 0)
+ return;
+
+ /* De-allocate TA and EE lists */
+ for (tlsa = dane->ta; tlsa; tlsa = next) {
+ next = tlsa->next;
+ tlsa_free(tlsa);
+ }
+ for (tlsa = dane->ee; tlsa; tlsa = next) {
+ next = tlsa->next;
+ tlsa_free(tlsa);
+ }
+
+ /* De-allocate full trust-anchor certs and pkeys */
+ free_ta_certs(dane);
+ free_ta_pkeys(dane);
+ if (dane->base_domain)
+ myfree(dane->base_domain);
+
+ myfree((void *) dane);
+}
+
+/* dane_free - ctable style */
+
+static void dane_free(void *dane, void *unused_context)
+{
+ tls_dane_free((TLS_DANE *) dane);
+}
+
+/* dane_locate - list head address of TLSA sublist for given algorithm */
+
+static TLS_TLSA **dane_locate(TLS_TLSA **tlsap, const char *mdalg)
+{
+ TLS_TLSA *new;
+
+ /*
+ * Correct computation of the session cache serverid requires a TLSA
+ * digest list that is sorted by algorithm name. Below we maintain the
+ * sort order (by algorithm name canonicalized to lowercase).
+ */
+ for (; *tlsap; tlsap = &(*tlsap)->next) {
+ int cmp = strcasecmp(mdalg, (*tlsap)->mdalg);
+
+ if (cmp == 0)
+ return (tlsap);
+ if (cmp < 0)
+ break;
+ }
+
+ new = (TLS_TLSA *) mymalloc(sizeof(*new));
+ new->mdalg = lowercase(mystrdup(mdalg));
+ new->certs = 0;
+ new->pkeys = 0;
+ new->next = *tlsap;
+ *tlsap = new;
+
+ return (tlsap);
+}
+
+/* tls_dane_add_ee_digests - split and append digests */
+
+void tls_dane_add_ee_digests(TLS_DANE *dane, const char *mdalg,
+ const char *digest, const char *delim)
+{
+ TLS_TLSA **tlsap = dane_locate(&dane->ee, mdalg);
+ TLS_TLSA *tlsa = *tlsap;
+
+ /* Delimited append, may append nothing */
+ if (tlsa->pkeys == 0)
+ tlsa->pkeys = argv_split(digest, delim);
+ else
+ argv_split_append(tlsa->pkeys, digest, delim);
+
+ /* Remove empty elements from the list */
+ if (tlsa->pkeys->argc == 0) {
+ argv_free(tlsa->pkeys);
+ tlsa->pkeys = 0;
+
+ if (tlsa->certs == 0) {
+ *tlsap = tlsa->next;
+ tlsa_free(tlsa);
+ }
+ return;
+ }
+
+ /*
+ * At the "fingerprint" security level certificate digests and public key
+ * digests are interchangeable. Each leaf certificate is matched via
+ * either the public key digest or full certificate digest. The DER
+ * encoding of a certificate is not a valid public key, and conversely,
+ * the DER encoding of a public key is not a valid certificate. An
+ * attacker would need a 2nd-preimage that is feasible across types
+ * (given cert digest == some pkey digest) and yet presumably difficult
+ * within a type (e.g. given cert digest == some other cert digest). No
+ * such attacks are known at this time, and it is expected that if any
+ * are found they would work within as well as across the cert/pkey data
+ * types.
+ */
+ if (tlsa->certs == 0)
+ tlsa->certs = argv_split(digest, delim);
+ else
+ argv_split_append(tlsa->certs, digest, delim);
+}
+
+/* dane_add - add a digest entry */
+
+static void dane_add(TLS_DANE *dane, int certusage, int selector,
+ const char *mdalg, char *digest)
+{
+ TLS_TLSA **tlsap;
+ TLS_TLSA *tlsa;
+ ARGV **argvp;
+
+ switch (certusage) {
+ case DNS_TLSA_USAGE_TRUST_ANCHOR_ASSERTION:
+ certusage = TLS_DANE_TA;
+ break;
+ case DNS_TLSA_USAGE_DOMAIN_ISSUED_CERTIFICATE:
+ certusage = TLS_DANE_EE; /* Collapse 1/3 -> 3 */
+ break;
+ default:
+ msg_panic("Unsupported DANE certificate usage: %d", certusage);
+ }
+
+ switch (selector) {
+ case DNS_TLSA_SELECTOR_FULL_CERTIFICATE:
+ selector = TLS_DANE_CERT;
+ break;
+ case DNS_TLSA_SELECTOR_SUBJECTPUBLICKEYINFO:
+ selector = TLS_DANE_PKEY;
+ break;
+ default:
+ msg_panic("Unsupported DANE selector: %d", selector);
+ }
+
+ tlsap = (certusage == TLS_DANE_EE) ? &dane->ee : &dane->ta;
+ tlsa = *(tlsap = dane_locate(tlsap, mdalg));
+ argvp = (selector == TLS_DANE_PKEY) ? &tlsa->pkeys : &tlsa->certs;
+
+ if (*argvp == 0)
+ *argvp = argv_alloc(1);
+ argv_add(*argvp, digest, ARGV_END);
+}
+
+#define FILTER_CTX_AGILITY_OK (1<<0)
+#define FILTER_CTX_APPLY_AGILITY (1<<1)
+#define FILTER_CTX_PARSE_DATA (1<<2)
+
+#define FILTER_RR_DROP 0
+#define FILTER_RR_KEEP 1
+
+typedef struct filter_ctx {
+ TLS_DANE *dane; /* Parsed result */
+ int count; /* Digest mtype count */
+ int target; /* Digest mtype target count */
+ int flags; /* Action/result bitmask */
+} filter_ctx;
+
+typedef int (*tlsa_filter) (DNS_RR *, filter_ctx *);
+
+/* tlsa_apply - apply filter to each rr in turn */
+
+static DNS_RR *tlsa_apply(DNS_RR *rr, tlsa_filter filter, filter_ctx *ctx)
+{
+ DNS_RR *head = 0; /* First retained RR */
+ DNS_RR *tail = 0; /* Last retained RR */
+ DNS_RR *next;
+
+ /*
+ * XXX Code that modifies or destroys DNS_RR lists or entries belongs in
+ * the DNS library, not here.
+ */
+ for ( /* nop */ ; rr; rr = next) {
+ next = rr->next;
+
+ if (filter(rr, ctx) == FILTER_RR_KEEP) {
+ tail = rr;
+ if (!head)
+ head = rr;
+ } else {
+ if (tail)
+ tail->next = rr->next;
+ rr->next = 0;
+ dns_rr_free(rr);
+ }
+ }
+ return (head);
+}
+
+/* usmdelta - packed usage/selector/mtype bits changing in next record */
+
+static unsigned int usmdelta(uint8_t u, uint8_t s, uint8_t m, DNS_RR *next)
+{
+ uint8_t *ip = (next && next->data_len >= 3) ? (uint8_t *) next->data : 0;
+ uint8_t nu = ip ? *ip++ : ~u;
+ uint8_t ns = ip ? *ip++ : ~s;
+ uint8_t nm = ip ? *ip++ : ~m;
+
+ return (((u ^ nu) << 16) | ((s ^ ns) << 8) | (m ^ nm));
+}
+
+/* tlsa_rr_cmp - qsort TLSA rrs in case shuffled by name server */
+
+static int tlsa_rr_cmp(DNS_RR *a, DNS_RR *b)
+{
+ int cmp;
+
+ /*
+ * Sort in ascending order, by usage, selector, matching type preference
+ * and payload. The usage, selector and matching type are the first
+ * three unsigned octets of the RR data.
+ */
+ if (a->data_len > 2 && b->data_len > 2) {
+ uint8_t *ai = (uint8_t *) a->data;
+ uint8_t *bi = (uint8_t *) b->data;
+
+#define signedcmp(x, y) (((int)(x)) - ((int)(y)))
+
+ if ((cmp = signedcmp(ai[0], bi[0])) != 0
+ || (cmp = signedcmp(ai[1], bi[1])) != 0
+ || (cmp = digest_pref_byid(ai[2]) -
+ digest_pref_byid(bi[2])) != 0)
+ return (cmp);
+ }
+ if ((cmp = a->data_len - b->data_len) != 0)
+ return (cmp);
+ return (memcmp(a->data, b->data, a->data_len));
+}
+
+/* parse_tlsa_rr - parse a validated TLSA RRset */
+
+static int parse_tlsa_rr(DNS_RR *rr, filter_ctx *ctx)
+{
+ uint8_t *ip;
+ uint8_t usage;
+ uint8_t selector;
+ uint8_t mtype;
+ ssize_t dlen;
+ const unsigned char *data;
+ const unsigned char *p;
+ int iscname = strcasecmp(rr->rname, rr->qname);
+ const char *q = (iscname) ? (rr)->qname : "";
+ const char *a = (iscname) ? " -> " : "";
+ const char *r = rr->rname;
+ unsigned int change;
+
+ if (rr->type != T_TLSA)
+ msg_panic("unexpected non-TLSA RR type %u for %s%s%s", rr->type,
+ q, a, r);
+
+ /* Drop truncated records */
+ if ((dlen = rr->data_len - 3) < 0) {
+ msg_warn("truncated length %u RR: %s%s%s IN TLSA ...",
+ (unsigned) rr->data_len, q, a, r);
+ ctx->flags &= ~FILTER_CTX_AGILITY_OK;
+ return (FILTER_RR_DROP);
+ }
+ ip = (uint8_t *) rr->data;
+ usage = *ip++;
+ selector = *ip++;
+ mtype = *ip++;
+ change = usmdelta(usage, selector, mtype, rr->next);
+ p = data = (const unsigned char *) ip;
+
+ /*
+ * Handle digest agility for non-zero matching types.
+ */
+ if (mtype) {
+ if (ctx->count && (ctx->flags & FILTER_CTX_APPLY_AGILITY)) {
+ if (change & 0xffff00) /* New usage/selector, */
+ ctx->count = 0; /* disable drop */
+ return (FILTER_RR_DROP);
+ }
+ }
+ /*-
+ * Drop unsupported usages.
+ * Note: NO SUPPORT for usages 0/1 which do not apply to SMTP.
+ */
+ switch (usage) {
+ case DNS_TLSA_USAGE_TRUST_ANCHOR_ASSERTION:
+ case DNS_TLSA_USAGE_DOMAIN_ISSUED_CERTIFICATE:
+ break;
+ default:
+ msg_warn("unsupported certificate usage %u in RR: "
+ "%s%s%s IN TLSA %u ...", usage,
+ q, a, r, usage);
+ return (FILTER_RR_DROP);
+ }
+
+ /*
+ * Drop unsupported selectors
+ */
+ switch (selector) {
+ case DNS_TLSA_SELECTOR_FULL_CERTIFICATE:
+ case DNS_TLSA_SELECTOR_SUBJECTPUBLICKEYINFO:
+ break;
+ default:
+ msg_warn("unsupported selector %u in RR: "
+ "%s%s%s IN TLSA %u %u ...", selector,
+ q, a, r, usage, selector);
+ return (FILTER_RR_DROP);
+ }
+
+ if (mtype) {
+ dane_digest *d = digest_byid(mtype);
+
+ if (d == 0) {
+ msg_warn("unsupported matching type %u in RR: "
+ "%s%s%s IN TLSA %u %u %u ...", mtype,
+ q, a, r, usage, selector, mtype);
+ return (FILTER_RR_DROP);
+ }
+ if (dlen != d->len) {
+ msg_warn("malformed %s digest, length %lu, in RR: "
+ "%s%s%s IN TLSA %u %u %u ...",
+ d->mdalg, (unsigned long) dlen,
+ q, a, r, usage, selector, mtype);
+ ctx->flags &= ~FILTER_CTX_AGILITY_OK;
+ return (FILTER_RR_DROP);
+ }
+ /* New digest mtype next? Prepare to drop following RRs */
+ if (change && (change & 0xffff00) == 0
+ && (ctx->flags & FILTER_CTX_APPLY_AGILITY))
+ ++ctx->count;
+
+ if (ctx->flags & FILTER_CTX_PARSE_DATA) {
+ char *digest = tls_digest_encode(data, dlen);
+
+ dane_add(ctx->dane, usage, selector, d->mdalg, digest);
+ if (msg_verbose || dane_verbose)
+ msg_info("using DANE RR: %s%s%s IN TLSA %u %u %u %s",
+ q, a, r, usage, selector, mtype, digest);
+ myfree(digest);
+ }
+ } else {
+ X509 *x = 0; /* OpenSSL re-uses *x if x!=0 */
+ EVP_PKEY *k = 0; /* OpenSSL re-uses *k if k!=0 */
+
+ /* Validate the cert or public key via d2i_mumble() */
+ switch (selector) {
+ case DNS_TLSA_SELECTOR_FULL_CERTIFICATE:
+ if (!d2i_X509(&x, &p, dlen) || dlen != p - data) {
+ msg_warn("malformed %s in RR: "
+ "%s%s%s IN TLSA %u %u %u ...", "certificate",
+ q, a, r, usage, selector, mtype);
+ if (x)
+ X509_free(x);
+ return (FILTER_RR_DROP);
+ }
+ /* Also unusable if public key is malformed or unsupported */
+ k = X509_get_pubkey(x);
+ EVP_PKEY_free(k);
+ if (k == 0) {
+ msg_warn("malformed %s in RR: %s%s%s IN TLSA %u %u %u ...",
+ "or unsupported certificate public key",
+ q, a, r, usage, selector, mtype);
+ X509_free(x);
+ return (FILTER_RR_DROP);
+ }
+
+ /*
+ * When a full trust-anchor certificate is published via DNS, we
+ * may need to use it to validate the server trust chain. Store
+ * it away for later use.
+ */
+ if (usage == DNS_TLSA_USAGE_TRUST_ANCHOR_ASSERTION
+ && (ctx->flags & FILTER_CTX_PARSE_DATA))
+ ta_cert_insert(ctx->dane, x);
+ X509_free(x);
+ break;
+
+ case DNS_TLSA_SELECTOR_SUBJECTPUBLICKEYINFO:
+ if (!d2i_PUBKEY(&k, &p, dlen) || dlen != p - data) {
+ msg_warn("malformed %s in RR: %s%s%s IN TLSA %u %u %u ...",
+ "public key", q, a, r, usage, selector, mtype);
+ if (k)
+ EVP_PKEY_free(k);
+ return (FILTER_RR_DROP);
+ }
+
+ /*
+ * When a full trust-anchor public key is published via DNS, we
+ * may need to use it to validate the server trust chain. Store
+ * it away for later use.
+ */
+ if (usage == DNS_TLSA_USAGE_TRUST_ANCHOR_ASSERTION
+ && (ctx->flags & FILTER_CTX_PARSE_DATA))
+ ta_pkey_insert(ctx->dane, k);
+ EVP_PKEY_free(k);
+ break;
+ }
+
+ /*
+ * The cert or key was valid, just digest the raw object, and encode
+ * the digest value.
+ */
+ if (ctx->flags & FILTER_CTX_PARSE_DATA) {
+ char *digest = tls_data_fprint((char *) data, dlen, signalg);
+
+ dane_add(ctx->dane, usage, selector, signalg, digest);
+ if (msg_verbose || dane_verbose)
+ msg_info("using DANE RR: %s%s%s IN TLSA %u %u %u <%s>; "
+ "%s digest %s", q, a, r, usage, selector, mtype,
+ (selector == DNS_TLSA_SELECTOR_FULL_CERTIFICATE) ?
+ "certificate" : "public key", signalg, digest);
+ myfree(digest);
+ }
+ }
+ return (FILTER_RR_KEEP);
+}
+
+/* process_rrs - filter and parse the TLSA RRset */
+
+static DNS_RR *process_rrs(TLS_DANE *dane, DNS_RR *rrset)
+{
+ filter_ctx ctx;
+
+ ctx.dane = dane;
+ ctx.count = ctx.target = 0;
+ ctx.flags = FILTER_CTX_APPLY_AGILITY | FILTER_CTX_PARSE_DATA;
+
+ rrset = tlsa_apply(rrset, parse_tlsa_rr, &ctx);
+
+ if (dane->ta == 0 && dane->ee == 0)
+ dane->flags |= TLS_DANE_FLAG_EMPTY;
+
+ return (rrset);
+}
+
+/* dane_lookup - TLSA record lookup, ctable style */
+
+static void *dane_lookup(const char *tlsa_fqdn, void *unused_ctx)
+{
+ static VSTRING *why = 0;
+ int ret;
+ DNS_RR *rrs = 0;
+ TLS_DANE *dane;
+
+ if (why == 0)
+ why = vstring_alloc(10);
+
+ dane = tls_dane_alloc();
+ ret = dns_lookup(tlsa_fqdn, T_TLSA, RES_USE_DNSSEC, &rrs, 0, why);
+
+ switch (ret) {
+ case DNS_OK:
+ if (TLS_DANE_CACHE_TTL_MIN && rrs->ttl < TLS_DANE_CACHE_TTL_MIN)
+ rrs->ttl = TLS_DANE_CACHE_TTL_MIN;
+ if (TLS_DANE_CACHE_TTL_MAX && rrs->ttl > TLS_DANE_CACHE_TTL_MAX)
+ rrs->ttl = TLS_DANE_CACHE_TTL_MAX;
+
+ /* One more second to account for discrete time */
+ dane->expires = 1 + event_time() + rrs->ttl;
+
+ if (rrs->dnssec_valid) {
+
+ /*
+ * Sort for deterministic digest in session cache lookup key. In
+ * addition we must arrange for more preferred matching types
+ * (full value or digest) to precede less preferred ones for the
+ * same usage and selector.
+ */
+ rrs = dns_rr_sort(rrs, tlsa_rr_cmp);
+ rrs = process_rrs(dane, rrs);
+ } else
+ dane->flags |= TLS_DANE_FLAG_NORRS;
+
+ if (rrs)
+ dns_rr_free(rrs);
+ break;
+
+ case DNS_NOTFOUND:
+ dane->flags |= TLS_DANE_FLAG_NORRS;
+ dane->expires = 1 + event_time() + TLS_DANE_CACHE_TTL_MIN;
+ break;
+
+ default:
+ msg_warn("DANE TLSA lookup problem: %s", STR(why));
+ dane->flags |= TLS_DANE_FLAG_ERROR;
+ break;
+ }
+
+ return (void *) dane;
+}
+
+/* resolve_host - resolve TLSA RRs for hostname (rname or qname) */
+
+static TLS_DANE *resolve_host(const char *host, const char *proto,
+ unsigned port)
+{
+ static VSTRING *query_domain;
+ TLS_DANE *dane;
+
+ if (query_domain == 0)
+ query_domain = vstring_alloc(64);
+
+ vstring_sprintf(query_domain, "_%u._%s.%s", ntohs(port), proto, host);
+ dane = (TLS_DANE *) ctable_locate(dane_cache, STR(query_domain));
+ if (timecmp(event_time(), dane->expires) > 0)
+ dane = (TLS_DANE *) ctable_refresh(dane_cache, STR(query_domain));
+ if (dane->base_domain == 0)
+ dane->base_domain = mystrdup(host);
+ /* Increment ref-count of cached entry */
+ ++dane->refs;
+ return (dane);
+}
+
+/* qname_secure - Lookup qname DNSSEC status */
+
+static int qname_secure(const char *qname)
+{
+ static VSTRING *why;
+ int ret = 0;
+ DNS_RR *rrs;
+
+ if (!why)
+ why = vstring_alloc(10);
+
+ /*
+ * We assume that qname is already an fqdn, and does not need any
+ * suffixes from RES_DEFNAME or RES_DNSRCH. This is typically the name
+ * of an MX host, and must be a complete DNS name. DANE initialization
+ * code in the SMTP client is responsible for checking that the default
+ * resolver flags do not include RES_DEFNAME and RES_DNSRCH.
+ */
+ ret = dns_lookup(qname, T_CNAME, RES_USE_DNSSEC, &rrs, 0, why);
+ if (ret == DNS_OK) {
+ ret = rrs->dnssec_valid;
+ dns_rr_free(rrs);
+ return (ret);
+ }
+ if (ret == DNS_NOTFOUND)
+ vstring_sprintf(why, "no longer a CNAME");
+ msg_warn("DNSSEC status lookup error for %s: %s", qname, STR(why));
+ return (-1);
+}
+
+/* tls_dane_resolve - cached map: (name, proto, port) -> TLS_DANE */
+
+TLS_DANE *tls_dane_resolve(unsigned port, const char *proto, DNS_RR *hostrr,
+ int forcetlsa)
+{
+ TLS_DANE *dane = 0;
+ int iscname = strcasecmp(hostrr->rname, hostrr->qname);
+ int isvalid = 1;
+
+ if (!tls_dane_avail())
+ return (0); /* Error */
+
+ /*
+ * By default suppress TLSA lookups for hosts in non-DNSSEC zones. If
+ * the host zone is not DNSSEC validated, the TLSA qname sub-domain is
+ * safely assumed to not be in a DNSSEC Look-aside Validation child zone.
+ */
+ if (!forcetlsa && !hostrr->dnssec_valid) {
+ isvalid = iscname ? qname_secure(hostrr->qname) : 0;
+ if (isvalid < 0)
+ return (0); /* Error */
+ }
+ if (!isvalid) {
+ dane = tls_dane_alloc();
+ dane->flags = TLS_DANE_FLAG_NORRS;
+ } else {
+ if (!dane_cache)
+ dane_cache = ctable_create(CACHE_SIZE, dane_lookup, dane_free, 0);
+
+ /*
+ * Try the rname first if secure, if nothing there, try the qname if
+ * different. Note, lookup errors are distinct from success with
+ * nothing found. If the rname lookup fails we don't try the qname.
+ */
+ if (hostrr->dnssec_valid) {
+ dane = resolve_host(hostrr->rname, proto, port);
+ if (tls_dane_notfound(dane) && iscname) {
+ tls_dane_free(dane);
+ dane = 0;
+ }
+ }
+ if (!dane)
+ dane = resolve_host(hostrr->qname, proto, port);
+ if (dane->flags & TLS_DANE_FLAG_ERROR) {
+ /* We don't return this object. */
+ tls_dane_free(dane);
+ dane = 0;
+ }
+ }
+
+ return (dane);
+}
+
+/* tls_dane_load_trustfile - load trust anchor certs or keys from file */
+
+int tls_dane_load_trustfile(TLS_DANE *dane, const char *tafile)
+{
+ BIO *bp;
+ char *name = 0;
+ char *header = 0;
+ unsigned char *data = 0;
+ long len;
+ int tacount;
+ char *errtype = 0; /* if error: cert or pkey? */
+ const char *mdalg;
+
+ /* nop */
+ if (tafile == 0 || *tafile == 0)
+ return (1);
+
+ if (!dane_initialized)
+ dane_init();
+
+ /* Per-destination TA support is available even when DANE is not */
+ mdalg = signalg ? signalg : "sha1";
+
+ /*
+ * On each call, PEM_read() wraps a stdio file in a BIO_NOCLOSE bio,
+ * calls PEM_read_bio() and then frees the bio. It is just as easy to
+ * open a BIO as a stdio file, so we use BIOs and call PEM_read_bio()
+ * directly.
+ */
+ if ((bp = BIO_new_file(tafile, "r")) == NULL) {
+ msg_warn("error opening trust anchor file: %s: %m", tafile);
+ return (0);
+ }
+ /* Don't report old news */
+ ERR_clear_error();
+
+ for (tacount = 0;
+ errtype == 0 && PEM_read_bio(bp, &name, &header, &data, &len);
+ ++tacount) {
+ const unsigned char *p = data;
+ int usage = DNS_TLSA_USAGE_TRUST_ANCHOR_ASSERTION;
+ int selector;
+ char *digest;
+
+ if (strcmp(name, PEM_STRING_X509) == 0
+ || strcmp(name, PEM_STRING_X509_OLD) == 0) {
+ X509 *cert = d2i_X509(0, &p, len);
+
+ if (cert && (p - data) == len) {
+ selector = DNS_TLSA_SELECTOR_FULL_CERTIFICATE;
+ digest = tls_data_fprint((char *) data, len, mdalg);
+ dane_add(dane, usage, selector, mdalg, digest);
+ myfree(digest);
+ ta_cert_insert(dane, cert);
+ } else
+ errtype = "certificate";
+ if (cert)
+ X509_free(cert);
+ } else if (strcmp(name, PEM_STRING_PUBLIC) == 0) {
+ EVP_PKEY *pkey = d2i_PUBKEY(0, &p, len);
+
+ if (pkey && (p - data) == len) {
+ selector = DNS_TLSA_SELECTOR_SUBJECTPUBLICKEYINFO;
+ digest = tls_data_fprint((char *) data, len, mdalg);
+ dane_add(dane, usage, selector, mdalg, digest);
+ myfree(digest);
+ ta_pkey_insert(dane, pkey);
+ } else
+ errtype = "public key";
+ if (pkey)
+ EVP_PKEY_free(pkey);
+ }
+
+ /*
+ * If any of these were null, PEM_read() would have failed.
+ */
+ OPENSSL_free(name);
+ OPENSSL_free(header);
+ OPENSSL_free(data);
+ }
+ BIO_free(bp);
+
+ if (errtype) {
+ tls_print_errors();
+ msg_warn("error reading: %s: malformed trust-anchor %s",
+ tafile, errtype);
+ return (0);
+ }
+ if (ERR_GET_REASON(ERR_peek_last_error()) == PEM_R_NO_START_LINE) {
+ /* Reached end of PEM file */
+ ERR_clear_error();
+ return (tacount > 0);
+ }
+ /* Some other PEM read error */
+ tls_print_errors();
+ return (0);
+}
+
+/* tls_dane_match - match cert against given list of TA or EE digests */
+
+int tls_dane_match(TLS_SESS_STATE *TLScontext, int usage,
+ X509 *cert, int depth)
+{
+ const TLS_DANE *dane = TLScontext->dane;
+ TLS_TLSA *tlsa = (usage == TLS_DANE_EE) ? dane->ee : dane->ta;
+ const char *namaddr = TLScontext->namaddr;
+ const char *ustr = (usage == TLS_DANE_EE) ? "end entity" : "trust anchor";
+ int matched;
+
+ for (matched = 0; tlsa && !matched; tlsa = tlsa->next) {
+ char **dgst;
+
+ /*
+ * Note, set_trust() 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 1
+#define MATCHED_PKEY 2
+
+ if (tlsa->pkeys) {
+ char *pkey_dgst = tls_pkey_fprint(cert, tlsa->mdalg);
+
+ for (dgst = tlsa->pkeys->argv; !matched && *dgst; ++dgst)
+ if (strcasecmp(pkey_dgst, *dgst) == 0)
+ matched = MATCHED_PKEY;
+ if (TLScontext->log_mask & (TLS_LOG_VERBOSE | TLS_LOG_CERTMATCH)
+ && matched)
+ msg_info("%s: depth=%d matched %s public-key %s digest=%s",
+ namaddr, depth, ustr, tlsa->mdalg, pkey_dgst);
+ myfree(pkey_dgst);
+ }
+ if (tlsa->certs != 0 && !matched) {
+ char *cert_dgst = tls_cert_fprint(cert, tlsa->mdalg);
+
+ for (dgst = tlsa->certs->argv; !matched && *dgst; ++dgst)
+ if (strcasecmp(cert_dgst, *dgst) == 0)
+ matched = MATCHED_CERT;
+ if (TLScontext->log_mask & (TLS_LOG_VERBOSE | TLS_LOG_CERTMATCH)
+ && matched)
+ msg_info("%s: depth=%d matched %s certificate %s digest %s",
+ namaddr, depth, ustr, tlsa->mdalg, cert_dgst);
+ myfree(cert_dgst);
+ }
+ }
+
+ return (matched);
+}
+
+/* add_ext - add simple extension (no config section references) */
+
+static int add_ext(X509 *issuer, X509 *subject, int ext_nid, char *ext_val)
+{
+ int ret = 0;
+ X509V3_CTX v3ctx;
+ X509_EXTENSION *ext;
+
+ X509V3_set_ctx(&v3ctx, issuer, subject, 0, 0, 0);
+ if ((ext = X509V3_EXT_conf_nid(0, &v3ctx, ext_nid, ext_val)) != 0) {
+ ret = X509_add_ext(subject, ext, -1);
+ X509_EXTENSION_free(ext);
+ }
+ return ret;
+}
+
+/* set_serial - set serial number to match akid or use subject's plus 1 */
+
+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);
+}
+
+/* add_akid - add authority key identifier */
+
+static int add_akid(X509 *cert, AUTHORITY_KEYID *akid)
+{
+ ASN1_OCTET_STRING *id;
+ unsigned char c = 0;
+ int nid = NID_authority_key_identifier;
+ 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 = ((akid && akid->keyid) ? akid->keyid : 0);
+ if (id && ASN1_STRING_length(id) == 1 && *ASN1_STRING_get0_data(id) == c)
+ c = 1;
+
+ if ((akid = AUTHORITY_KEYID_new()) != 0
+ && (akid->keyid = ASN1_OCTET_STRING_new()) != 0
+ && ASN1_OCTET_STRING_set(akid->keyid, (void *) &c, 1)
+ && X509_add1_ext_i2d(cert, nid, akid, 0, X509V3_ADD_DEFAULT) > 0)
+ ret = 1;
+ if (akid)
+ AUTHORITY_KEYID_free(akid);
+ return (ret);
+}
+
+/* add_skid - add subject key identifier to match child's akid */
+
+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"));
+ else
+ return (X509_add1_ext_i2d(cert, nid, akid->keyid, 0,
+ X509V3_ADD_DEFAULT) > 0);
+}
+
+/* akid_issuer_name - get akid issuer directory name */
+
+static X509_NAME *akid_issuer_name(AUTHORITY_KEYID *akid)
+{
+ if (akid && akid->issuer) {
+ int i;
+ general_name_stack_t *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);
+}
+
+/* set_issuer - set issuer DN to match akid if specified */
+
+static int set_issuer_name(X509 *cert, AUTHORITY_KEYID *akid, X509_NAME *subj)
+{
+ X509_NAME *name = akid_issuer_name(akid);
+
+ /*
+ * If subject's akid specifies an authority key identifier issuer name,
+ * we must use that.
+ */
+ if (name)
+ return (X509_set_issuer_name(cert, name));
+ return (X509_set_issuer_name(cert, subj));
+}
+
+/* grow_chain - add certificate to trusted or untrusted chain */
+
+static void grow_chain(TLS_SESS_STATE *TLScontext, int trusted, X509 *cert)
+{
+ x509_stack_t **xs = trusted ? &TLScontext->trusted : &TLScontext->untrusted;
+
+#define UNTRUSTED 0
+#define TRUSTED 1
+
+ if (!*xs && (*xs = sk_X509_new_null()) == 0)
+ msg_fatal("out of memory");
+ if (cert) {
+ if (trusted && !X509_add1_trust_object(cert, serverAuth))
+ msg_fatal("out of memory");
+ X509_up_ref(cert);
+ if (!sk_X509_push(*xs, cert))
+ msg_fatal("out of memory");
+ }
+}
+
+/* wrap_key - wrap TA "key" as issuer of "subject" */
+
+static void wrap_key(TLS_SESS_STATE *TLScontext, int depth,
+ EVP_PKEY *key, X509 *subject)
+{
+ X509 *cert = 0;
+ AUTHORITY_KEYID *akid;
+ X509_NAME *name = X509_get_issuer_name(subject);
+
+ /*
+ * The subject name is never a NULL object unless we run out of memory.
+ * It may be an empty sequence, but the containing object always exists
+ * and its storage is owned by the certificate itself.
+ */
+ if (name == 0 || (cert = X509_new()) == 0)
+ msg_fatal("Out of memory");
+
+ /*
+ * Record the depth of the intermediate wrapper certificate, logged in
+ * the verify callback.
+ */
+ if (TLScontext->tadepth < 0) {
+ TLScontext->tadepth = depth + 1;
+ if (TLScontext->log_mask & (TLS_LOG_VERBOSE | TLS_LOG_CERTMATCH))
+ msg_info("%s: depth=%d chain is trust-anchor signed",
+ TLScontext->namaddr, depth);
+ }
+ akid = X509_get_ext_d2i(subject, NID_authority_key_identifier, 0, 0);
+
+ ERR_clear_error();
+
+ /* CA cert valid for +/- 30 days. */
+ if (!X509_set_version(cert, 2)
+ || !set_serial(cert, akid, subject)
+ || !set_issuer_name(cert, akid, name)
+ || !X509_gmtime_adj(X509_getm_notBefore(cert), -30 * 86400L)
+ || !X509_gmtime_adj(X509_getm_notAfter(cert), 30 * 86400L)
+ || !X509_set_subject_name(cert, name)
+ || !X509_set_pubkey(cert, key)
+ || !add_ext(0, cert, NID_basic_constraints, "CA:TRUE")
+ || (key && !add_akid(cert, akid))
+ || !add_skid(cert, akid)) {
+ tls_print_errors();
+ msg_fatal("error generating DANE wrapper certificate");
+ }
+ if (akid)
+ AUTHORITY_KEYID_free(akid);
+ grow_chain(TLScontext, TRUSTED, cert);
+ if (cert)
+ X509_free(cert);
+}
+
+/* wrap_cert - wrap "tacert" as trust-anchor. */
+
+static void wrap_cert(TLS_SESS_STATE *TLScontext, X509 *tacert, int depth)
+{
+ if (TLScontext->tadepth < 0)
+ TLScontext->tadepth = depth + 1;
+
+ if (TLScontext->log_mask & (TLS_LOG_VERBOSE | TLS_LOG_CERTMATCH))
+ msg_info("%s: depth=%d trust-anchor certificate",
+ TLScontext->namaddr, depth);
+
+ grow_chain(TLScontext, TRUSTED, tacert);
+ return;
+}
+
+/* ta_signed - is certificate signed by a TLSA cert or pkey */
+
+static int ta_signed(TLS_SESS_STATE *TLScontext, X509 *cert, int depth)
+{
+ const TLS_DANE *dane = TLScontext->dane;
+ EVP_PKEY *pk;
+ TLS_PKEYS *k;
+ TLS_CERTS *x;
+ 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->cert, cert) == X509_V_OK) {
+ if ((pk = X509_get_pubkey(x->cert)) == 0)
+ continue;
+ /* Check signature, since some other TA may work if not this. */
+ if ((done = (X509_verify(cert, pk) > 0)) != 0)
+ wrap_cert(TLScontext, x->cert, depth);
+ 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 ((done = (X509_verify(cert, k->pkey) > 0)) != 0)
+ wrap_key(TLScontext, depth, k->pkey, cert);
+ else
+ ERR_clear_error();
+
+ return (done);
+}
+
+/* set_trust - configure for DANE validation */
+
+static void set_trust(TLS_SESS_STATE *TLScontext, X509_STORE_CTX *ctx)
+{
+ int n;
+ int i;
+ int match;
+ int depth = 0;
+ EVP_PKEY *takey;
+ X509 *ca;
+ X509 *cert = X509_STORE_CTX_get0_cert(ctx);
+ x509_stack_t *in = X509_STORE_CTX_get0_untrusted(ctx);
+
+ /* shallow copy */
+ if ((in = sk_X509_dup(in)) == 0)
+ msg_fatal("out of memory");
+
+ /*
+ * 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);
+
+ /* Is it a trust anchor? */
+ match = tls_dane_match(TLScontext, TLS_DANE_TA, ca, depth + 1);
+ if (match) {
+ switch (match) {
+ case MATCHED_CERT:
+ wrap_cert(TLScontext, ca, depth);
+ break;
+ case MATCHED_PKEY:
+ if ((takey = X509_get_pubkey(ca)) == 0)
+ msg_panic("trust-anchor certificate has null pkey");
+ wrap_key(TLScontext, depth, takey, cert);
+ EVP_PKEY_free(takey);
+ break;
+ default:
+ msg_panic("unexpected tls_dane_match result: %d", match);
+ }
+ cert = 0;
+ break;
+ }
+ /* Add untrusted ca. */
+ grow_chain(TLScontext, UNTRUSTED, ca);
+
+ /* Final untrusted self-signed element? */
+ if (X509_check_issued(ca, ca) == X509_V_OK) {
+ cert = 0;
+ break;
+ }
+ /* Restart with issuer as subject */
+ cert = ca;
+ }
+
+ /*
+ * 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. Otherwise, we found no TAs and no
+ * issuer, so set an empty list of TAs.
+ */
+ if (!cert || !ta_signed(TLScontext, cert, depth)) {
+ /* Create empty trust list if null, else NOP */
+ grow_chain(TLScontext, TRUSTED, 0);
+ }
+ /* shallow free */
+ if (in)
+ sk_X509_free(in);
+}
+
+/* dane_cb - wrap chain verification for DANE */
+
+static int dane_cb(X509_STORE_CTX *ctx, void *app_ctx)
+{
+ const char *myname = "dane_cb";
+ TLS_SESS_STATE *TLScontext = (TLS_SESS_STATE *) app_ctx;
+ X509 *cert = X509_STORE_CTX_get0_cert(ctx);
+
+ /*
+ * Degenerate case: depth 0 self-signed cert.
+ *
+ * XXX: Should we suppress name checks, ... when the leaf certificate is a
+ * TA. After all they could sign any name they want. However, this
+ * requires a bit of additional code. For now we allow depth 0 TAs, but
+ * then the peer name has to match.
+ */
+ if (X509_check_issued(cert, cert) == X509_V_OK) {
+
+ /*
+ * Empty untrusted chain, could be NULL, but then ABI check less
+ * reliable, we may zero some other field, ...
+ */
+ grow_chain(TLScontext, UNTRUSTED, 0);
+ if (tls_dane_match(TLScontext, TLS_DANE_TA, cert, 0)) {
+ TLScontext->tadepth = 0;
+ grow_chain(TLScontext, TRUSTED, cert);
+ } else
+ grow_chain(TLScontext, TRUSTED, 0);
+ } else {
+ set_trust(TLScontext, ctx);
+ }
+
+ /*
+ * Check that setting the untrusted chain updates the expected structure
+ * member at the expected offset.
+ */
+ X509_STORE_CTX_set0_trusted_stack(ctx, TLScontext->trusted);
+ X509_STORE_CTX_set0_untrusted(ctx, TLScontext->untrusted);
+ if (X509_STORE_CTX_get0_untrusted(ctx) != TLScontext->untrusted)
+ msg_panic("%s: OpenSSL ABI change", myname);
+
+ return X509_verify_cert(ctx);
+}
+
+/* tls_dane_set_callback - set or clear verification wrapper callback */
+
+void tls_dane_set_callback(SSL_CTX *ctx, TLS_SESS_STATE *TLScontext)
+{
+ if (TLS_DANE_HASTA(TLScontext->dane))
+ SSL_CTX_set_cert_verify_callback(ctx, dane_cb, (void *) TLScontext);
+ else
+ SSL_CTX_set_cert_verify_callback(ctx, 0, 0);
+}
+
+#ifdef TEST
+
+#include <unistd.h>
+#include <stdarg.h>
+
+#include <mail_params.h>
+#include <mail_conf.h>
+#include <msg_vstream.h>
+
+static int verify_chain(SSL *ssl, x509_stack_t *chain, TLS_SESS_STATE *tctx)
+{
+ int ret;
+ X509 *cert;
+ X509_STORE_CTX *store_ctx;
+ SSL_CTX *ssl_ctx = SSL_get_SSL_CTX(ssl);
+ X509_STORE *store = SSL_CTX_get_cert_store(ssl_ctx);
+ int store_ctx_idx = SSL_get_ex_data_X509_STORE_CTX_idx();
+
+ cert = sk_X509_value(chain, 0);
+ if ((store_ctx = X509_STORE_CTX_new()) == NULL) {
+ SSLerr(SSL_F_SSL_VERIFY_CERT_CHAIN, ERR_R_MALLOC_FAILURE);
+ return 0;
+ }
+ if (!X509_STORE_CTX_init(store_ctx, store, cert, chain)) {
+ X509_STORE_CTX_free(store_ctx);
+ return 0;
+ }
+ X509_STORE_CTX_set_ex_data(store_ctx, store_ctx_idx, ssl);
+
+ X509_STORE_CTX_set_default(store_ctx, "ssl_server");
+ X509_VERIFY_PARAM_set1(X509_STORE_CTX_get0_param(store_ctx),
+ SSL_get0_param(ssl));
+
+ if (SSL_get_verify_callback(ssl))
+ X509_STORE_CTX_set_verify_cb(store_ctx, SSL_get_verify_callback(ssl));
+
+ ret = dane_cb(store_ctx, tctx);
+
+ SSL_set_verify_result(ssl, X509_STORE_CTX_get_error(store_ctx));
+ X509_STORE_CTX_free(store_ctx);
+
+ return (ret);
+}
+
+static void add_tlsa(TLS_DANE *dane, char *argv[])
+{
+ char *digest;
+ X509 *cert = 0;
+ BIO *bp;
+ unsigned char *buf;
+ unsigned char *buf2;
+ int len;
+ uint8_t u = atoi(argv[1]);
+ uint8_t s = atoi(argv[2]);
+ const char *mdname = argv[3];
+ EVP_PKEY *pkey;
+
+ /* Unsupported usages are fatal */
+ switch (u) {
+ case DNS_TLSA_USAGE_TRUST_ANCHOR_ASSERTION:
+ case DNS_TLSA_USAGE_DOMAIN_ISSUED_CERTIFICATE:
+ break;
+ default:
+ msg_fatal("unsupported certificate usage %u", u);
+ }
+
+ /* Unsupported selectors are fatal */
+ switch (s) {
+ case DNS_TLSA_SELECTOR_FULL_CERTIFICATE:
+ case DNS_TLSA_SELECTOR_SUBJECTPUBLICKEYINFO:
+ break;
+ default:
+ msg_fatal("unsupported selector %u", s);
+ }
+
+ /* Unsupported digests are fatal */
+ if (*mdname && !tls_validate_digest(mdname))
+ msg_fatal("unsupported digest algorithm: %s", mdname);
+
+ if ((bp = BIO_new_file(argv[4], "r")) == NULL)
+ msg_fatal("error opening %s: %m", argv[4]);
+ if (!PEM_read_bio_X509(bp, &cert, 0, 0)) {
+ tls_print_errors();
+ msg_fatal("error loading certificate from %s: %m", argv[4]);
+ }
+ BIO_free(bp);
+
+ /*
+ * Extract ASN.1 DER form of certificate or public key.
+ */
+ switch (s) {
+ case DNS_TLSA_SELECTOR_FULL_CERTIFICATE:
+ len = i2d_X509(cert, NULL);
+ buf2 = buf = (unsigned char *) mymalloc(len);
+ i2d_X509(cert, &buf2);
+ if (!*mdname)
+ ta_cert_insert(dane, cert);
+ break;
+ case DNS_TLSA_SELECTOR_SUBJECTPUBLICKEYINFO:
+ pkey = X509_get_pubkey(cert);
+ len = i2d_PUBKEY(pkey, NULL);
+ buf2 = buf = (unsigned char *) mymalloc(len);
+ i2d_PUBKEY(pkey, &buf2);
+ if (!*mdname)
+ ta_pkey_insert(dane, pkey);
+ EVP_PKEY_free(pkey);
+ break;
+ }
+ OPENSSL_assert(buf2 - buf == len);
+
+ digest = tls_data_fprint((char *) buf, len, *mdname ? mdname : signalg);
+ dane_add(dane, u, s, *mdname ? mdname : signalg, digest);
+ myfree((void *) digest);
+ myfree((void *) buf);
+}
+
+static x509_stack_t *load_chain(const char *chainfile)
+{
+ BIO *bp;
+ char *name = 0;
+ char *header = 0;
+ unsigned char *data = 0;
+ long len;
+ int count;
+ char *errtype = 0; /* if error: cert or pkey? */
+ x509_stack_t *chain;
+ typedef X509 *(*d2i_X509_t) (X509 **, const unsigned char **, long);
+
+ if ((chain = sk_X509_new_null()) == 0) {
+ perror("malloc");
+ exit(1);
+ }
+
+ /*
+ * On each call, PEM_read() wraps a stdio file in a BIO_NOCLOSE bio,
+ * calls PEM_read_bio() and then frees the bio. It is just as easy to
+ * open a BIO as a stdio file, so we use BIOs and call PEM_read_bio()
+ * directly.
+ */
+ if ((bp = BIO_new_file(chainfile, "r")) == NULL) {
+ fprintf(stderr, "error opening chainfile: %s: %m\n", chainfile);
+ exit(1);
+ }
+ /* Don't report old news */
+ ERR_clear_error();
+
+ for (count = 0;
+ errtype == 0 && PEM_read_bio(bp, &name, &header, &data, &len);
+ ++count) {
+ const unsigned char *p = data;
+
+ if (strcmp(name, PEM_STRING_X509) == 0
+ || strcmp(name, PEM_STRING_X509_TRUSTED) == 0
+ || strcmp(name, PEM_STRING_X509_OLD) == 0) {
+ d2i_X509_t d;
+ X509 *cert;
+
+ d = strcmp(name, PEM_STRING_X509_TRUSTED) ? d2i_X509_AUX : d2i_X509;
+ if ((cert = d(0, &p, len)) == 0 || (p - data) != len)
+ errtype = "certificate";
+ else if (sk_X509_push(chain, cert) == 0) {
+ perror("malloc");
+ exit(1);
+ }
+ } else {
+ fprintf(stderr, "unexpected chain file object: %s\n", name);
+ exit(1);
+ }
+
+ /*
+ * If any of these were null, PEM_read() would have failed.
+ */
+ OPENSSL_free(name);
+ OPENSSL_free(header);
+ OPENSSL_free(data);
+ }
+ BIO_free(bp);
+
+ if (errtype) {
+ tls_print_errors();
+ fprintf(stderr, "error reading: %s: malformed %s", chainfile, errtype);
+ exit(1);
+ }
+ if (ERR_GET_REASON(ERR_peek_last_error()) == PEM_R_NO_START_LINE) {
+ /* Reached end of PEM file */
+ ERR_clear_error();
+ if (count > 0)
+ return chain;
+ fprintf(stderr, "no certificates found in: %s\n", chainfile);
+ exit(1);
+ }
+ /* Some other PEM read error */
+ tls_print_errors();
+ fprintf(stderr, "error reading: %s\n", chainfile);
+ exit(1);
+}
+
+static void usage(const char *progname)
+{
+ fprintf(stderr, "Usage: %s certificate-usage selector matching-type"
+ " certfile \\\n\t\tCAfile chainfile hostname [certname ...]\n",
+ progname);
+ fprintf(stderr, " where, certificate-usage = TLSA certificate usage,\n");
+ fprintf(stderr, "\t selector = TLSA selector,\n");
+ fprintf(stderr, "\t matching-type = empty string or OpenSSL digest algorithm name,\n");
+ fprintf(stderr, "\t PEM certfile provides certificate association data,\n");
+ fprintf(stderr, "\t PEM CAfile contains any usage 0/1 trusted roots,\n");
+ fprintf(stderr, "\t PEM chainfile = server chain file to verify\n");
+ fprintf(stderr, "\t hostname = destination hostname,\n");
+ fprintf(stderr, "\t each certname augments the hostname for name checks.\n");
+ exit(1);
+}
+
+/* match_servername - match servername against pattern */
+
+static int match_servername(const char *certid, ARGV *margv)
+{
+ const char *domain;
+ const char *parent;
+ int match_subdomain;
+ int i;
+ int idlen;
+ int domlen;
+
+ /*
+ * XXX EAI support.
+ */
+
+ /*
+ * Match the certid against each pattern until we find a match.
+ */
+ for (i = 0; i < margv->argc; ++i) {
+ match_subdomain = 0;
+ domain = margv->argv[i];
+ 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 var_tls_multi_label 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(var_tls_multi_wildcard == 0 ? parent :
+ parent + domlen - idlen,
+ certid + 1) == 0))
+ return (1);
+ }
+ return (0);
+}
+
+static void check_name(TLS_SESS_STATE *tctx, X509 *cert, ARGV *margs)
+{
+ char *cn;
+ int matched = 0;
+ general_name_stack_t *gens;
+
+ if (SSL_get_verify_result(tctx->con) != X509_V_OK)
+ return;
+
+ tctx->peer_status |= TLS_CERT_FLAG_TRUSTED;
+
+ gens = X509_get_ext_d2i(cert, NID_subject_alt_name, 0, 0);
+ if (gens) {
+ int has_dnsname = 0;
+ int num_gens = sk_GENERAL_NAME_num(gens);
+ int i;
+
+ for (i = 0; !matched && i < num_gens; ++i) {
+ const GENERAL_NAME *gn = sk_GENERAL_NAME_value(gens, i);
+ const char *dnsname;
+
+ if (gn->type != GEN_DNS)
+ continue;
+ has_dnsname = 1;
+ tctx->peer_status |= TLS_CERT_FLAG_ALTNAME;
+ dnsname = tls_dns_name(gn, tctx);
+ if (dnsname && *dnsname
+ && (matched = match_servername(dnsname, margs)) != 0)
+ tctx->peer_status |= TLS_CERT_FLAG_MATCHED;
+ }
+ sk_GENERAL_NAME_pop_free(gens, GENERAL_NAME_free);
+ if (has_dnsname)
+ return;
+ }
+ cn = tls_peer_CN(cert, tctx);
+ if (match_servername(cn, margs))
+ tctx->peer_status |= TLS_CERT_FLAG_MATCHED;
+ myfree(cn);
+}
+
+static void check_print(TLS_SESS_STATE *tctx, X509 *cert)
+{
+ if (TLS_DANE_HASEE(tctx->dane)
+ && tls_dane_match(tctx, TLS_DANE_EE, cert, 0))
+ tctx->peer_status |= TLS_CERT_FLAG_TRUSTED | TLS_CERT_FLAG_MATCHED;
+}
+
+static void check_peer(TLS_SESS_STATE *tctx, X509 *cert, int argc, char **argv)
+{
+ ARGV match;
+
+ tctx->peer_status |= TLS_CERT_FLAG_PRESENT;
+ check_print(tctx, cert);
+ if (!TLS_CERT_IS_MATCHED(tctx)) {
+ match.argc = argc;
+ match.argv = argv;
+ check_name(tctx, cert, &match);
+ }
+}
+
+static SSL_CTX *ctx_init(const char *CAfile)
+{
+ SSL_CTX *client_ctx;
+
+ tls_param_init();
+ tls_check_version();
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ SSL_load_error_strings();
+ SSL_library_init();
+#endif
+
+ if (!tls_validate_digest(LN_sha1))
+ msg_fatal("%s digest algorithm not available", LN_sha1);
+
+ if (TLScontext_index < 0)
+ if ((TLScontext_index = SSL_get_ex_new_index(0, 0, 0, 0, 0)) < 0)
+ msg_fatal("Cannot allocate SSL application data index");
+
+ ERR_clear_error();
+ if ((client_ctx = SSL_CTX_new(TLS_client_method())) == 0)
+ msg_fatal("cannot allocate client SSL_CTX");
+ SSL_CTX_set_verify_depth(client_ctx, 5);
+
+ if (tls_set_ca_certificate_info(client_ctx, CAfile, "") < 0) {
+ tls_print_errors();
+ msg_fatal("cannot load CAfile: %s", CAfile);
+ }
+ SSL_CTX_set_verify(client_ctx, SSL_VERIFY_NONE,
+ tls_verify_certificate_callback);
+ return (client_ctx);
+}
+
+int main(int argc, char *argv[])
+{
+ SSL_CTX *ssl_ctx;
+ TLS_SESS_STATE *tctx;
+ x509_stack_t *chain;
+
+ var_procname = mystrdup(basename(argv[0]));
+ set_mail_conf_str(VAR_PROCNAME, var_procname);
+ msg_vstream_init(var_procname, VSTREAM_OUT);
+
+ if (argc < 8)
+ usage(argv[0]);
+
+ ssl_ctx = ctx_init(argv[5]);
+ if (!tls_dane_avail())
+ msg_fatal("DANE TLSA support not available");
+
+ tctx = tls_alloc_sess_context(TLS_LOG_NONE, argv[7]);
+ tctx->namaddr = argv[7];
+ tctx->mdalg = LN_sha1;
+ tctx->dane = tls_dane_alloc();
+
+ if ((tctx->con = SSL_new(ssl_ctx)) == 0
+ || !SSL_set_ex_data(tctx->con, TLScontext_index, tctx)) {
+ tls_print_errors();
+ msg_fatal("Error allocating SSL connection");
+ }
+ SSL_set_connect_state(tctx->con);
+ add_tlsa((TLS_DANE *) tctx->dane, argv);
+ tls_dane_set_callback(ssl_ctx, tctx);
+
+ /* Verify saved server chain */
+ chain = load_chain(argv[6]);
+ verify_chain(tctx->con, chain, tctx);
+ check_peer(tctx, sk_X509_value(chain, 0), argc - 7, argv + 7);
+ tls_print_errors();
+
+ msg_info("%s %s", TLS_CERT_IS_MATCHED(tctx) ? "Verified" :
+ TLS_CERT_IS_TRUSTED(tctx) ? "Trusted" : "Untrusted", argv[7]);
+
+ return (TLS_CERT_IS_MATCHED(tctx) ? 0 : 1);
+}
+
+#endif /* TEST */
+
+#endif /* USE_TLS */