static void dane_init(SMTP_TLS_POLICY *tls, SMTP_ITERATOR *iter) { TLS_DANE *dane; if (!iter->port) { msg_warn("%s: the \"dane\" security level is invalid for delivery via" " unix-domain sockets", STR(iter->dest)); MARK_INVALID(tls->why, &tls->level); return; } if (!tls_dane_avail()) { dane_incompat(tls, iter, NONDANE_CONFIG, "%s: %s configured, but no requisite library support", STR(iter->dest), policy_name(tls->level)); return; } if (!(smtp_host_lookup_mask & SMTP_HOST_FLAG_DNS) || smtp_dns_support != SMTP_DNS_DNSSEC) { dane_incompat(tls, iter, NONDANE_CONFIG, "%s: %s configured with dnssec lookups disabled", STR(iter->dest), policy_name(tls->level)); return; } /* * If we ignore MX lookup errors, we also ignore DNSSEC security problems * and thus avoid any reasonable expectation that we get the right DANE * key material. */ if (smtp_mode && var_ign_mx_lookup_err) { dane_incompat(tls, iter, NONDANE_CONFIG, "%s: %s configured with MX lookup errors ignored", STR(iter->dest), policy_name(tls->level)); return; } /* * This is not optional, code in tls_dane.c assumes that the nexthop * qname is already an fqdn. If we're using these flags to go from qname * to rname, the assumption is invalid. Likewise we cannot add the qname * to certificate name checks, ... */ if (smtp_dns_res_opt & (RES_DEFNAMES | RES_DNSRCH)) { dane_incompat(tls, iter, NONDANE_CONFIG, "%s: dns resolver options incompatible with %s TLS", STR(iter->dest), policy_name(tls->level)); return; } /* When the MX name is present and insecure, DANE does not apply. */ if (iter->mx && !iter->mx->dnssec_valid) { dane_incompat(tls, iter, NONDANE_DEST, "non DNSSEC destination"); return; } /* When TLSA lookups fail, we defer the message */ if ((dane = tls_dane_resolve(iter->port, "tcp", iter->rr, var_smtp_tls_force_tlsa)) == 0) { tls->level = TLS_LEV_INVALID; dsb_simple(tls->why, "4.7.5", "TLSA lookup error for %s:%u", STR(iter->host), ntohs(iter->port)); return; } if (tls_dane_notfound(dane)) { dane_incompat(tls, iter, NONDANE_DEST, "no TLSA records found"); tls_dane_free(dane); return; } /* * Some TLSA records found, but none usable, per * * https://tools.ietf.org/html/draft-ietf-dane-srv-02#section-4 * * we MUST use TLS, and SHALL use full PKIX certificate checks. The latter * would be unwise for SMTP: no human present to "click ok" and risk of * non-delivery in most cases exceeds risk of interception. * * We also have a form of Goedel's incompleteness theorem in play: any list * of public root CA certs is either incomplete or inconsistent (for any * given verifier some of the CAs are surely not trustworthy). */ if (tls_dane_unusable(dane)) { dane_incompat(tls, iter, DANE_UNUSABLE, "TLSA records unusable"); tls_dane_free(dane); return; } /* * With DANE trust anchors, peername matching is not configurable. */ if (TLS_DANE_HASTA(dane)) { tls->matchargv = argv_alloc(2); argv_add(tls->matchargv, dane->base_domain, ARGV_END); if (iter->mx) { if (strcmp(iter->mx->qname, iter->mx->rname) == 0) argv_add(tls->matchargv, iter->mx->qname, ARGV_END); else argv_add(tls->matchargv, iter->mx->rname, iter->mx->qname, ARGV_END); } } else if (!TLS_DANE_HASEE(dane)) msg_panic("empty DANE match list"); tls->dane = dane; tls->level = TLS_LEV_DANE; return; }
/* * This is the actual startup routine for the connection. We expect that the * buffers are flushed and the "220 Ready to start TLS" was received by us, * so that we can immediately start the TLS handshake process. */ TLS_SESS_STATE *tls_client_start(const TLS_CLIENT_START_PROPS *props) { int sts; int protomask; const char *cipher_list; SSL_SESSION *session = 0; SSL_CIPHER_const SSL_CIPHER *cipher; X509 *peercert; TLS_SESS_STATE *TLScontext; TLS_APPL_STATE *app_ctx = props->ctx; char *myserverid; int log_mask = app_ctx->log_mask; /* * When certificate verification is required, log trust chain validation * errors even when disabled by default for opportunistic sessions. For * DANE this only applies when using trust-anchor associations. */ if (TLS_MUST_TRUST(props->tls_level) && (!TLS_DANE_BASED(props->tls_level) || TLS_DANE_HASTA(props->dane))) log_mask |= TLS_LOG_UNTRUSTED; if (log_mask & TLS_LOG_VERBOSE) msg_info("setting up TLS connection to %s", props->namaddr); /* * First make sure we have valid protocol and cipher parameters * * Per-session protocol restrictions must be applied to the SSL connection, * as restrictions in the global context cannot be cleared. */ protomask = tls_protocol_mask(props->protocols); if (protomask == TLS_PROTOCOL_INVALID) { /* tls_protocol_mask() logs no warning. */ msg_warn("%s: Invalid TLS protocol list \"%s\": aborting TLS session", props->namaddr, props->protocols); return (0); } /* DANE requires SSLv3 or later, not SSLv2. */ if (TLS_DANE_BASED(props->tls_level)) protomask |= TLS_PROTOCOL_SSLv2; /* * Per session cipher selection for sessions with mandatory encryption * * The cipherlist is applied to the global SSL context, since it is likely * to stay the same between connections, so we make use of a 1-element * cache to return the same result for identical inputs. */ cipher_list = tls_set_ciphers(app_ctx, "TLS", props->cipher_grade, props->cipher_exclusions); if (cipher_list == 0) { msg_warn("%s: %s: aborting TLS session", props->namaddr, vstring_str(app_ctx->why)); return (0); } if (log_mask & TLS_LOG_VERBOSE) msg_info("%s: TLS cipher list \"%s\"", props->namaddr, cipher_list); /* * OpenSSL will ignore cached sessions that use the wrong protocol. So we * do not need to filter out cached sessions with the "wrong" protocol, * rather OpenSSL will simply negotiate a new session. * * We salt the session lookup key with the protocol list, so that sessions * found in the cache are plausibly acceptable. * * By the time a TLS client is negotiating ciphers it has already offered to * re-use a session, it is too late to renege on the offer. So we must * not attempt to re-use sessions whose ciphers are too weak. We salt the * session lookup key with the cipher list, so that sessions found in the * cache are always acceptable. * * With DANE, (more generally any TLScontext where we specified explicit * trust-anchor or end-entity certificates) the verification status of * the SSL session depends on the specified list. Since we verify the * certificate only during the initial handshake, we must segregate * sessions with different TA lists. Note, that TA re-verification is * not possible with cached sessions, since these don't hold the complete * peer trust chain. Therefore, we compute a digest of the sorted TA * parameters and append it to the serverid. */ myserverid = tls_serverid_digest(props, protomask, cipher_list); /* * Allocate a new TLScontext for the new connection and get an SSL * structure. Add the location of TLScontext to the SSL to later retrieve * the information inside the tls_verify_certificate_callback(). * * If session caching was enabled when TLS was initialized, the cache type * is stored in the client SSL context. */ TLScontext = tls_alloc_sess_context(log_mask, props->namaddr); TLScontext->cache_type = app_ctx->cache_type; TLScontext->serverid = myserverid; TLScontext->stream = props->stream; TLScontext->mdalg = props->mdalg; /* Alias DANE digest info from props */ TLScontext->dane = props->dane; if ((TLScontext->con = SSL_new(app_ctx->ssl_ctx)) == NULL) { msg_warn("Could not allocate 'TLScontext->con' with SSL_new()"); tls_print_errors(); tls_free_context(TLScontext); return (0); } if (!SSL_set_ex_data(TLScontext->con, TLScontext_index, TLScontext)) { msg_warn("Could not set application data for 'TLScontext->con'"); tls_print_errors(); tls_free_context(TLScontext); return (0); } /* * Apply session protocol restrictions. */ if (protomask != 0) SSL_set_options(TLScontext->con, TLS_SSL_OP_PROTOMASK(protomask)); /* * XXX To avoid memory leaks we must always call SSL_SESSION_free() after * calling SSL_set_session(), regardless of whether or not the session * will be reused. */ if (TLScontext->cache_type) { session = load_clnt_session(TLScontext); if (session) { SSL_set_session(TLScontext->con, session); SSL_SESSION_free(session); /* 200411 */ } } #ifdef TLSEXT_MAXLEN_host_name if (TLS_DANE_BASED(props->tls_level) && strlen(props->host) <= TLSEXT_MAXLEN_host_name) { /* * With DANE sessions, send an SNI hint. We don't care whether the * server reports finding a matching certificate or not, so no * callback is required to process the server response. Our use of * SNI is limited to giving servers that are (mis)configured to use * SNI the best opportunity to find the certificate they promised via * the associated TLSA RRs. (Generally, server administrators should * avoid SNI, and there are no plans to support SNI in the Postfix * SMTP server). * * Since the hostname is DNSSEC-validated, it must be a DNS FQDN and * thererefore valid for use with SNI. Failure to set a valid SNI * hostname is a memory allocation error, and thus transient. Since * we must not cache the session if we failed to send the SNI name, * we have little choice but to abort. */ if (!SSL_set_tlsext_host_name(TLScontext->con, props->host)) { msg_warn("%s: error setting SNI hostname to: %s", props->namaddr, props->host); tls_free_context(TLScontext); return (0); } if (log_mask & TLS_LOG_DEBUG) msg_info("%s: SNI hostname: %s", props->namaddr, props->host); } #endif /* * Before really starting anything, try to seed the PRNG a little bit * more. */ tls_int_seed(); (void) tls_ext_seed(var_tls_daemon_rand_bytes); /* * Initialize the SSL connection to connect state. This should not be * necessary anymore since 0.9.3, but the call is still in the library * and maintaining compatibility never hurts. */ SSL_set_connect_state(TLScontext->con); /* * Connect the SSL connection with the network socket. */ if (SSL_set_fd(TLScontext->con, vstream_fileno(props->stream)) != 1) { msg_info("SSL_set_fd error to %s", props->namaddr); tls_print_errors(); uncache_session(app_ctx->ssl_ctx, TLScontext); tls_free_context(TLScontext); return (0); } /* * Turn on non-blocking I/O so that we can enforce timeouts on network * I/O. */ non_blocking(vstream_fileno(props->stream), NON_BLOCKING); /* * If the debug level selected is high enough, all of the data is dumped: * TLS_LOG_TLSPKTS will dump the SSL negotiation, TLS_LOG_ALLPKTS will * dump everything. * * We do have an SSL_set_fd() and now suddenly a BIO_ routine is called? * Well there is a BIO below the SSL routines that is automatically * created for us, so we can use it for debugging purposes. */ if (log_mask & TLS_LOG_TLSPKTS) BIO_set_callback(SSL_get_rbio(TLScontext->con), tls_bio_dump_cb); tls_dane_set_callback(app_ctx->ssl_ctx, TLScontext); /* * Start TLS negotiations. This process is a black box that invokes our * call-backs for certificate verification. * * Error handling: If the SSL handhake fails, we print out an error message * and remove all TLS state concerning this session. */ sts = tls_bio_connect(vstream_fileno(props->stream), props->timeout, TLScontext); if (sts <= 0) { if (ERR_peek_error() != 0) { msg_info("SSL_connect error to %s: %d", props->namaddr, sts); tls_print_errors(); } else if (errno != 0) { msg_info("SSL_connect error to %s: %m", props->namaddr); } else { msg_info("SSL_connect error to %s: lost connection", props->namaddr); } uncache_session(app_ctx->ssl_ctx, TLScontext); tls_free_context(TLScontext); return (0); } /* Turn off packet dump if only dumping the handshake */ if ((log_mask & TLS_LOG_ALLPKTS) == 0) BIO_set_callback(SSL_get_rbio(TLScontext->con), 0); /* * The caller may want to know if this session was reused or if a new * session was negotiated. */ TLScontext->session_reused = SSL_session_reused(TLScontext->con); if ((log_mask & TLS_LOG_CACHE) && TLScontext->session_reused) msg_info("%s: Reusing old session", TLScontext->namaddr); /* * Do peername verification if requested and extract useful information * from the certificate for later use. */ if ((peercert = SSL_get_peer_certificate(TLScontext->con)) != 0) { TLScontext->peer_status |= TLS_CERT_FLAG_PRESENT; /* * Peer name or fingerprint verification as requested. * Unconditionally set peer_CN, issuer_CN and peer_cert_fprint. Check * fingerprint first, and avoid logging verified as untrusted in the * call to verify_extract_name(). */ verify_extract_print(TLScontext, peercert, props); verify_extract_name(TLScontext, peercert, props); if (TLScontext->log_mask & (TLS_LOG_CERTMATCH | TLS_LOG_VERBOSE | TLS_LOG_PEERCERT)) msg_info("%s: subject_CN=%s, issuer_CN=%s, " "fingerprint=%s, pkey_fingerprint=%s", props->namaddr, TLScontext->peer_CN, TLScontext->issuer_CN, TLScontext->peer_cert_fprint, TLScontext->peer_pkey_fprint); X509_free(peercert); } else { TLScontext->issuer_CN = mystrdup(""); TLScontext->peer_CN = mystrdup(""); TLScontext->peer_cert_fprint = mystrdup(""); TLScontext->peer_pkey_fprint = mystrdup(""); } /* * Finally, collect information about protocol and cipher for logging */ TLScontext->protocol = SSL_get_version(TLScontext->con); cipher = SSL_get_current_cipher(TLScontext->con); TLScontext->cipher_name = SSL_CIPHER_get_name(cipher); TLScontext->cipher_usebits = SSL_CIPHER_get_bits(cipher, &(TLScontext->cipher_algbits)); /* * The TLS engine is active. Switch to the tls_timed_read/write() * functions and make the TLScontext available to those functions. */ tls_stream_start(props->stream, TLScontext); /* * All the key facts in a single log entry. */ if (log_mask & TLS_LOG_SUMMARY) msg_info("%s TLS connection established to %s: %s with cipher %s " "(%d/%d bits)", !TLS_CERT_IS_PRESENT(TLScontext) ? "Anonymous" : TLS_CERT_IS_MATCHED(TLScontext) ? "Verified" : TLS_CERT_IS_TRUSTED(TLScontext) ? "Trusted" : "Untrusted", props->namaddr, TLScontext->protocol, TLScontext->cipher_name, TLScontext->cipher_usebits, TLScontext->cipher_algbits); tls_int_seed(); return (TLScontext); }
static void *policy_create(const char *unused_key, void *context) { SMTP_ITERATOR *iter = (SMTP_ITERATOR *) context; int site_level; const char *dest = STR(iter->dest); const char *host = STR(iter->host); /* * Prepare a pristine policy object. */ SMTP_TLS_POLICY *tls = (SMTP_TLS_POLICY *) mymalloc(sizeof(*tls)); smtp_tls_policy_init(tls, dsb_create()); /* * Compute the per-site TLS enforcement level. For compatibility with the * original TLS patch, this algorithm is gives equal precedence to host * and next-hop policies. */ tls->level = global_tls_level(); site_level = TLS_LEV_NOTFOUND; if (tls_policy) { tls_policy_lookup(tls, &site_level, dest, "next-hop destination"); } else if (tls_per_site) { tls_site_lookup(tls, &site_level, dest, "next-hop destination"); if (site_level != TLS_LEV_INVALID && strcasecmp(dest, host) != 0) tls_site_lookup(tls, &site_level, host, "server hostname"); /* * Override a wild-card per-site policy with a more specific global * policy. * * With the original TLS patch, 1) a per-site ENCRYPT could not override * a global VERIFY, and 2) a combined per-site (NONE+MAY) policy * produced inconsistent results: it changed a global VERIFY into * NONE, while producing MAY with all weaker global policy settings. * * With the current implementation, a combined per-site (NONE+MAY) * consistently overrides global policy with NONE, and global policy * can override only a per-site MAY wildcard. That is, specific * policies consistently override wildcard policies, and * (non-wildcard) per-site policies consistently override global * policies. */ if (site_level == TLS_LEV_MAY && tls->level > TLS_LEV_MAY) site_level = tls->level; } switch (site_level) { default: tls->level = site_level; case TLS_LEV_NOTFOUND: break; case TLS_LEV_INVALID: return ((void *) tls); } /* * DANE initialization may change the security level to something else, * so do this early, so that we use the right level below. Note that * "dane-only" changes to "dane" once we obtain the requisite TLSA * records. */ if (tls->level == TLS_LEV_DANE || tls->level == TLS_LEV_DANE_ONLY) dane_init(tls, iter); if (tls->level == TLS_LEV_INVALID) return ((void *) tls); /* * Use main.cf protocols setting if not set in per-destination table. */ if (tls->level > TLS_LEV_NONE && tls->protocols == 0) tls->protocols = mystrdup((tls->level == TLS_LEV_MAY) ? var_smtp_tls_proto : var_smtp_tls_mand_proto); /* * Compute cipher grade (if set in per-destination table, else * set_cipher() uses main.cf settings) and security level dependent * cipher exclusion list. */ set_cipher_grade(tls); /* * Use main.cf cert_match setting if not set in per-destination table. */ switch (tls->level) { case TLS_LEV_INVALID: case TLS_LEV_NONE: case TLS_LEV_MAY: case TLS_LEV_ENCRYPT: case TLS_LEV_DANE: break; case TLS_LEV_FPRINT: if (tls->dane == 0) tls->dane = tls_dane_alloc(); if (!TLS_DANE_HASEE(tls->dane)) { tls_dane_add_ee_digests(tls->dane, var_smtp_tls_fpt_dgst, var_smtp_tls_fpt_cmatch, "\t\n\r, "); if (!TLS_DANE_HASEE(tls->dane)) { msg_warn("nexthop domain %s: configured at fingerprint " "security level, but with no fingerprints to match.", dest); MARK_INVALID(tls->why, &tls->level); return ((void *) tls); } } break; case TLS_LEV_VERIFY: case TLS_LEV_SECURE: if (tls->matchargv == 0) tls->matchargv = argv_split(tls->level == TLS_LEV_VERIFY ? var_smtp_tls_vfy_cmatch : var_smtp_tls_sec_cmatch, "\t\n\r, :"); if (*var_smtp_tls_tafile) { if (tls->dane == 0) tls->dane = tls_dane_alloc(); if (!TLS_DANE_HASTA(tls->dane) && !load_tas(tls->dane, var_smtp_tls_tafile)) { MARK_INVALID(tls->why, &tls->level); return ((void *) tls); } } break; default: msg_panic("unexpected TLS security level: %d", tls->level); } if (msg_verbose && tls->level != global_tls_level()) msg_info("%s TLS level: %s", "effective", policy_name(tls->level)); return ((void *) tls); }