#ifndef DISABLE_OCSP
# include <openssl/ocsp.h>
#endif
+#ifdef EXPERIMENTAL_DANE
+# include <danessl.h>
+#endif
+
#ifndef DISABLE_OCSP
# define EXIM_OCSP_SKEW_SECONDS (300L)
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);
}
+#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.dane_verified =
+ tls_out.certificate_verified = TRUE;
+return 1;
+}
+
+#endif /*EXPERIMENTAL_DANE*/
+
/*************************************************
* Information callback *
#endif /*!DISABLE_OCSP*/
-
/*************************************************
* Initialize for TLS *
*************************************************/
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)
return OK;
}
+
+#ifdef EXPERIMENTAL_DANE
+static int
+tlsa_lookup(host_item * host, dns_answer * dnsa,
+ BOOL dane_required, BOOL * dane)
+{
+/* move this out to host.c given the similarity to dns_lookup() ? */
+uschar buffer[300];
+uschar * fullname = buffer;
+
+/* TLSA lookup string */
+(void)sprintf(CS buffer, "_%d._tcp.%.256s", host->port, host->name);
+
+switch (dns_lookup(dnsa, buffer, T_TLSA, &fullname))
+ {
+ case DNS_AGAIN:
+ return DEFER; /* just defer this TLS'd conn */
+
+ default:
+ case DNS_FAIL:
+ if (dane_required)
+ {
+ log_write(0, LOG_MAIN, "DANE error: TLSA lookup failed");
+ return FAIL;
+ }
+ break;
+
+ case DNS_SUCCEED:
+ if (!dns_is_secure(dnsa))
+ {
+ log_write(0, LOG_MAIN, "DANE error: TLSA lookup not DNSSEC");
+ return DEFER;
+ }
+ *dane = TRUE;
+ break;
+ }
+return OK;
+}
+
+
+static int
+dane_tlsa_load(SSL * ssl, host_item * host, dns_answer * dnsa)
+{
+dns_record * rr;
+dns_scan dnss;
+const char * hostnames[2] = { CS host->name, NULL };
+int found = 0;
+
+if (DANESSL_init(ssl, NULL, hostnames) != 1)
+ return tls_error(US"hostnames load", host, NULL);
+
+for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
+ rr;
+ rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)
+ ) if (rr->type == T_TLSA)
+ {
+ uschar * p = rr->data;
+ uint8_t usage, selector, mtype;
+ const char * mdname;
+
+ found++;
+ usage = *p++;
+ selector = *p++;
+ mtype = *p++;
+
+ switch (mtype)
+ {
+ default:
+ log_write(0, LOG_MAIN,
+ "DANE error: TLSA record w/bad mtype 0x%x", mtype);
+ return FAIL;
+ case 0: mdname = NULL; break;
+ case 1: mdname = "sha256"; break;
+ case 2: mdname = "sha512"; break;
+ }
+
+ switch (DANESSL_add_tlsa(ssl, usage, selector, mdname, p, rr->size - 3))
+ {
+ default:
+ case 0: /* action not taken */
+ return tls_error(US"tlsa load", host, NULL);
+ case 1: break;
+ }
+
+ tls_out.tlsa_usage |= 1<<usage;
+ }
+
+if (found)
+ return OK;
+
+log_write(0, LOG_MAIN, "DANE error: No TLSA records");
+return FAIL;
+}
+#endif /*EXPERIMENTAL_DANE*/
+
+
+
/*************************************************
* Start a TLS session in a client *
*************************************************/
{
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];
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;*/
+tls_out.dane_verified = FALSE;
+tls_out.tlsa_usage = 0;
+dane_required = verify_check_this_host(&ob->hosts_require_dane, NULL,
+ host->name, host->address, NULL) == OK;
-if (!dane_in_use)
+if (host->dnssec == DS_YES)
+ {
+ if( dane_required
+ || verify_check_this_host(&ob->hosts_try_dane, NULL,
+ host->name, host->address, NULL) == OK
+ )
+ if ((rc = tlsa_lookup(host, &tlsa_dnsa, dane_required, &dane)) != OK)
+ return rc;
+ }
+else if (dane_required)
+ {
+ /*XXX a shame we only find this after making tcp & smtp connection */
+ /* move the test earlier? */
+ log_write(0, LOG_MAIN, "DANE error: previous lookup not DNSSEC");
+ return FAIL;
+ }
#endif
#ifndef DISABLE_OCSP
{
- 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
}
#ifdef EXPERIMENTAL_DANE
-if (dane_in_use)
+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, 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
}
}
+#ifdef EXPERIMENTAL_DANE
+if (dane)
+ if ((rc = dane_tlsa_load(client_ssl, host, &tlsa_dnsa)) != OK)
+ return rc;
+#endif
+
#ifndef DISABLE_OCSP
/* Request certificate status at connection-time. If the server
does OCSP stapling we will get the callback (set in tls_init()) */
+# ifdef EXPERIMENTAL_DANE
if (request_ocsp)
{
- SSL_set_tlsext_status_type(client_ssl, TLSEXT_STATUSTYPE_ocsp);
- client_static_cbinfo->u_ocsp.client.verify_required = require_ocsp;
- tls_out.ocsp = OCSP_NOT_RESP;
+ const uschar * s;
+ if ( (s = ob->hosts_require_ocsp) && Ustrstr(s, US"tls_out_tlsa_usage")
+ || (s = ob->hosts_request_ocsp) && Ustrstr(s, US"tls_out_tlsa_usage")
+ )
+ { /* Re-eval now $tls_out_tlsa_usage is populated. If
+ this means we avoid the OCSP request, we wasted the setup
+ cost in tls_init(). */
+ require_ocsp = verify_check_this_host(&ob->hosts_require_ocsp,
+ NULL, host->name, host->address, NULL) == OK;
+ request_ocsp = require_ocsp ? TRUE
+ : verify_check_this_host(&ob->hosts_request_ocsp,
+ NULL, host->name, host->address, NULL) == OK;
+ }
}
-#endif
+# endif
-#ifdef EXPERIMENTAL_DANE
-if (dane_in_use)
+if (request_ocsp)
{
- 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)
- */
+ SSL_set_tlsext_status_type(client_ssl, TLSEXT_STATUSTYPE_ocsp);
+ client_static_cbinfo->u_ocsp.client.verify_required = require_ocsp;
+ tls_out.ocsp = OCSP_NOT_RESP;
}
#endif
alarm(0);
#ifdef EXPERIMENTAL_DANE
-DANESSL_cleanup(client_ssl); /*XXX earliest possible callpoint. Too early? */
+if (dane)
+ DANESSL_cleanup(client_ssl);
#endif
if (rc <= 0)