#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/rand.h>
-#ifdef EXPERIMENTAL_OCSP
-#include <openssl/ocsp.h>
+#ifndef DISABLE_OCSP
+# include <openssl/ocsp.h>
#endif
+#ifdef EXPERIMENTAL_DANE
+# include <danessl.h>
+#endif
+
-#ifdef EXPERIMENTAL_OCSP
-#define EXIM_OCSP_SKEW_SECONDS (300L)
-#define EXIM_OCSP_MAX_AGE (-1L)
+#ifndef DISABLE_OCSP
+# define EXIM_OCSP_SKEW_SECONDS (300L)
+# define EXIM_OCSP_MAX_AGE (-1L)
#endif
#if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT)
-#define EXIM_HAVE_OPENSSL_TLSEXT
+# define EXIM_HAVE_OPENSSL_TLSEXT
+#endif
+#if OPENSSL_VERSION_NUMBER >= 0x010100000L
+# define EXIM_HAVE_OPENSSL_CHECKHOST
+#endif
+#if OPENSSL_VERSION_NUMBER >= 0x010000000L \
+ && (OPENSSL_VERSION_NUMBER & 0x0000ff000L) >= 0x000002000L
+# define EXIM_HAVE_OPENSSL_CHECKHOST
+#endif
+
+#if !defined(EXIM_HAVE_OPENSSL_TLSEXT) && !defined(DISABLE_OCSP)
+# warning "OpenSSL library version too old; define DISABLE_OCSP in Makefile"
+# define DISABLE_OCSP
#endif
/* Structure for collecting random data for seeding. */
typedef struct tls_ext_ctx_cb {
uschar *certificate;
uschar *privatekey;
-#ifdef EXPERIMENTAL_OCSP
+#ifndef DISABLE_OCSP
BOOL is_server;
union {
struct {
OCSP_RESPONSE *response;
} server;
struct {
- X509_STORE *verify_store;
+ X509_STORE *verify_store; /* non-null if status requested */
+ BOOL verify_required;
} client;
} u_ocsp;
#endif
uschar *server_cipher_list;
/* only passed down to tls_error: */
host_item *host;
+ const uschar * verify_cert_hostnames;
+#ifdef EXPERIMENTAL_EVENT
+ uschar * event_action;
+#endif
} tls_ext_ctx_cb;
/* should figure out a cleanup of API to handle state preserved per
#ifdef EXIM_HAVE_OPENSSL_TLSEXT
static int tls_servername_cb(SSL *s, int *ad ARG_UNUSED, void *arg);
#endif
-#ifdef EXPERIMENTAL_OCSP
+#ifndef DISABLE_OCSP
static int tls_server_stapling_cb(SSL *s, void *arg);
#endif
*/
static int
-tls_error(uschar *prefix, host_item *host, uschar *msg)
+tls_error(uschar * prefix, const host_item * host, uschar * msg)
{
-if (msg == NULL)
+if (!msg)
{
ERR_error_string(ERR_get_error(), ssl_errstring);
msg = (uschar *)ssl_errstring;
}
-if (host == NULL)
+if (host)
+ {
+ log_write(0, LOG_MAIN, "H=%s [%s] TLS error on connection (%s): %s",
+ host->name, host->address, prefix, msg);
+ return FAIL;
+ }
+else
{
uschar *conn_info = smtp_get_connection_info();
if (Ustrncmp(conn_info, US"SMTP ", 5) == 0)
conn_info += 5;
+ /* I'd like to get separated H= here, but too hard for now */
log_write(0, LOG_MAIN, "TLS error on %s (%s): %s",
conn_info, prefix, msg);
return DEFER;
}
-else
- {
- log_write(0, LOG_MAIN, "TLS error on connection to %s [%s] (%s): %s",
- host->name, host->address, prefix, msg);
- return FAIL;
- }
}
/* Extreme debug
-#if defined(EXPERIMENTAL_OCSP)
+#ifndef DISABLE_OCSP
void
x509_store_dump_cert_s_names(X509_STORE * store)
{
{
X509 * current_cert= tmp_obj->data.x509;
X509_NAME_oneline(X509_get_subject_name(current_cert), CS name, sizeof(name));
+ name[sizeof(name)-1] = '\0';
debug_printf(" %s\n", name);
}
}
*/
+#ifdef EXPERIMENTAL_EVENT
+static int
+verify_event(tls_support * tlsp, X509 * cert, int depth, const uschar * dn,
+ BOOL *calledp, const BOOL *optionalp, const uschar * what)
+{
+uschar * ev;
+uschar * yield;
+X509 * old_cert;
+
+ev = tlsp == &tls_out ? client_static_cbinfo->event_action : event_action;
+if (ev)
+ {
+ old_cert = tlsp->peercert;
+ tlsp->peercert = X509_dup(cert);
+ /* NB we do not bother setting peerdn */
+ if ((yield = event_raise(ev, US"tls:cert", string_sprintf("%d", depth))))
+ {
+ log_write(0, LOG_MAIN, "[%s] %s verify denied by event-action: "
+ "depth=%d cert=%s: %s",
+ tlsp == &tls_out ? deliver_host_address : sender_host_address,
+ what, depth, dn, yield);
+ *calledp = TRUE;
+ if (!*optionalp)
+ {
+ if (old_cert) tlsp->peercert = old_cert; /* restore 1st failing cert */
+ return 1; /* reject (leaving peercert set) */
+ }
+ DEBUG(D_tls) debug_printf("Event-action verify failure overridden "
+ "(host in tls_try_verify_hosts)\n");
+ }
+ X509_free(tlsp->peercert);
+ tlsp->peercert = old_cert;
+ }
+return 0;
+}
+#endif
+
/*************************************************
* Callback for verification *
*************************************************/
/* The SSL library does certificate verification if set up to do so. This
callback has the current yes/no state is in "state". If verification succeeded,
-we set up the tls_peerdn string. If verification failed, what happens depends
-on whether the client is required to present a verifiable certificate or not.
+we set the certificate-verified flag. If verification failed, what happens
+depends on whether the client is required to present a verifiable certificate
+or not.
If verification is optional, we change the state to yes, but still log the
verification error. For some reason (it really would help to have proper
documentation of OpenSSL), this callback function then gets called again, this
-time with state = 1. In fact, that's useful, because we can set up the peerdn
-value, but we must take care not to set the private verified flag on the second
-time through.
+time with state = 1. We must take care not to set the private verified flag on
+the second time through.
Note: this function is not called if the client fails to present a certificate
when asked. We get here only if a certificate has been received. Handling of
optional verification for this case is done when requesting SSL to verify, by
setting SSL_VERIFY_FAIL_IF_NO_PEER_CERT in the non-optional case.
+May be called multiple times for different issues with a certificate, even
+for a given "depth" in the certificate chain.
+
Arguments:
state current yes/no state as 1/0
x509ctx certificate information.
*/
static int
-verify_callback(int state, X509_STORE_CTX *x509ctx, tls_support *tlsp, BOOL *calledp, BOOL *optionalp)
+verify_callback(int state, X509_STORE_CTX *x509ctx,
+ tls_support *tlsp, BOOL *calledp, BOOL *optionalp)
{
-static uschar txt[256];
+X509 * cert = X509_STORE_CTX_get_current_cert(x509ctx);
+int depth = X509_STORE_CTX_get_error_depth(x509ctx);
+uschar dn[256];
-X509_NAME_oneline(X509_get_subject_name(x509ctx->current_cert),
- CS txt, sizeof(txt));
+X509_NAME_oneline(X509_get_subject_name(cert), CS dn, sizeof(dn));
+dn[sizeof(dn)-1] = '\0';
if (state == 0)
{
- log_write(0, LOG_MAIN, "SSL verify error: depth=%d error=%s cert=%s",
- x509ctx->error_depth,
- X509_verify_cert_error_string(x509ctx->error),
- txt);
- tlsp->certificate_verified = FALSE;
+ log_write(0, LOG_MAIN, "[%s] SSL verify error: depth=%d error=%s cert=%s",
+ tlsp == &tls_out ? deliver_host_address : sender_host_address,
+ depth,
+ X509_verify_cert_error_string(X509_STORE_CTX_get_error(x509ctx)),
+ dn);
*calledp = TRUE;
- if (!*optionalp) return 0; /* reject */
+ if (!*optionalp)
+ {
+ if (!tlsp->peercert)
+ tlsp->peercert = X509_dup(cert); /* record failing cert */
+ return 0; /* reject */
+ }
DEBUG(D_tls) debug_printf("SSL verify failure overridden (host in "
"tls_try_verify_hosts)\n");
- return 1; /* accept */
}
-if (x509ctx->error_depth != 0)
+else if (depth != 0)
{
- DEBUG(D_tls) debug_printf("SSL verify ok: depth=%d cert=%s\n",
- x509ctx->error_depth, txt);
-#ifdef EXPERIMENTAL_OCSP
+ DEBUG(D_tls) debug_printf("SSL verify ok: depth=%d SN=%s\n", depth, dn);
+#ifndef DISABLE_OCSP
if (tlsp == &tls_out && client_static_cbinfo->u_ocsp.client.verify_store)
{ /* client, wanting stapling */
/* Add the server cert's signing chain as the one
for the verification of the OCSP stapled information. */
if (!X509_STORE_add_cert(client_static_cbinfo->u_ocsp.client.verify_store,
- x509ctx->current_cert))
+ cert))
ERR_clear_error();
}
+#endif
+#ifdef EXPERIMENTAL_EVENT
+ if (verify_event(tlsp, cert, depth, dn, calledp, optionalp, US"SSL"))
+ return 0; /* reject, with peercert set */
#endif
}
else
{
- DEBUG(D_tls) debug_printf("SSL%s peer: %s\n",
- *calledp ? "" : " authenticated", txt);
- tlsp->peerdn = txt;
- }
+ const uschar * verify_cert_hostnames;
-/*XXX JGH: this looks bogus - we set "verified" first time through, which
-will be for the root CS cert (calls work down the chain). Why should it
-not be on the last call, where we're setting peerdn?
+ if ( tlsp == &tls_out
+ && ((verify_cert_hostnames = client_static_cbinfo->verify_cert_hostnames)))
+ /* client, wanting hostname check */
+ {
-To test: set up a chain anchored by a good root-CA but with a bad server cert.
-Does certificate_verified get set?
-*/
-if (!*calledp) tlsp->certificate_verified = TRUE;
-*calledp = TRUE;
+#if EXIM_HAVE_OPENSSL_CHECKHOST
+# ifndef X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS
+# define X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS 0
+# endif
+# ifndef X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS
+# define X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS 0
+# endif
+ int sep = 0;
+ const uschar * list = verify_cert_hostnames;
+ uschar * name;
+ int rc;
+ while ((name = string_nextinlist(&list, &sep, NULL, 0)))
+ if ((rc = X509_check_host(cert, name, 0,
+ X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS
+ | X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS)))
+ {
+ if (rc < 0)
+ {
+ log_write(0, LOG_MAIN, "[%s] SSL verify error: internal error",
+ tlsp == &tls_out ? deliver_host_address : sender_host_address);
+ name = NULL;
+ }
+ break;
+ }
+ if (!name)
+#else
+ if (!tls_is_name_for_cert(verify_cert_hostnames, cert))
+#endif
+ {
+ log_write(0, LOG_MAIN,
+ "[%s] SSL verify error: certificate name mismatch: \"%s\"",
+ tlsp == &tls_out ? deliver_host_address : sender_host_address,
+ dn);
+ *calledp = TRUE;
+ if (!*optionalp)
+ {
+ if (!tlsp->peercert)
+ tlsp->peercert = X509_dup(cert); /* record failing cert */
+ return 0; /* reject */
+ }
+ DEBUG(D_tls) debug_printf("SSL verify failure overridden (host in "
+ "tls_try_verify_hosts)\n");
+ }
+ }
-return 1; /* accept */
+#ifdef EXPERIMENTAL_EVENT
+ if (verify_event(tlsp, cert, depth, dn, calledp, optionalp, US"SSL"))
+ return 0; /* reject, with peercert set */
+#endif
+
+ DEBUG(D_tls) debug_printf("SSL%s verify ok: depth=0 SN=%s\n",
+ *calledp ? "" : " authenticated", dn);
+ if (!*calledp) tlsp->certificate_verified = TRUE;
+ *calledp = TRUE;
+ }
+
+return 1; /* accept, at least for this level */
}
static int
}
+#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);
+uschar dn[256];
+#ifdef EXPERIMENTAL_EVENT
+int depth = X509_STORE_CTX_get_error_depth(x509ctx);
+uschar * yield;
+BOOL dummy_called, optional = FALSE;
+#endif
+
+X509_NAME_oneline(X509_get_subject_name(cert), CS dn, sizeof(dn));
+dn[sizeof(dn)-1] = '\0';
+
+DEBUG(D_tls) debug_printf("verify_callback_client_dane: %s\n", dn);
+
+#ifdef EXPERIMENTAL_EVENT
+ if (verify_event(&tls_out, cert, depth, dn,
+ &dummy_called, &optional, US"DANE"))
+ return 0; /* reject, with peercert set */
+#endif
+
+if (state == 1)
+ tls_out.dane_verified =
+ tls_out.certificate_verified = TRUE;
+return 1;
+}
+
+#endif /*EXPERIMENTAL_DANE*/
+
/*************************************************
* Information callback *
*/
static BOOL
-init_dh(SSL_CTX *sctx, uschar *dhparam, host_item *host)
+init_dh(SSL_CTX *sctx, uschar *dhparam, const host_item *host)
{
BIO *bio;
DH *dh;
if (!expand_check(dhparam, US"tls_dhparam", &dhexpanded))
return FALSE;
-if (dhexpanded == NULL || *dhexpanded == '\0')
- {
+if (!dhexpanded || !*dhexpanded)
bio = BIO_new_mem_buf(CS std_dh_prime_default(), -1);
- }
else if (dhexpanded[0] == '/')
{
- bio = BIO_new_file(CS dhexpanded, "r");
- if (bio == NULL)
+ if (!(bio = BIO_new_file(CS dhexpanded, "r")))
{
tls_error(string_sprintf("could not read dhparams file %s", dhexpanded),
host, US strerror(errno));
return TRUE;
}
- pem = std_dh_prime_named(dhexpanded);
- if (!pem)
+ if (!(pem = std_dh_prime_named(dhexpanded)))
{
tls_error(string_sprintf("Unknown standard DH prime \"%s\"", dhexpanded),
host, US strerror(errno));
bio = BIO_new_mem_buf(CS pem, -1);
}
-dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
-if (dh == NULL)
+if (!(dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL)))
{
BIO_free(bio);
tls_error(string_sprintf("Could not read tls_dhparams \"%s\"", dhexpanded),
-#ifdef EXPERIMENTAL_OCSP
+#ifndef DISABLE_OCSP
/*************************************************
* Load OCSP information into state *
*************************************************/
}
supply_response:
-cbinfo->u_ocsp.server.response = resp;
+ cbinfo->u_ocsp.server.response = resp;
return;
bad:
-if (running_in_test_harness)
- {
- extern char ** environ;
- uschar ** p;
- for (p = USS environ; *p != NULL; p++)
- if (Ustrncmp(*p, "EXIM_TESTHARNESS_DISABLE_OCSPVALIDITYCHECK", 42) == 0)
- {
- DEBUG(D_tls) debug_printf("Supplying known bad OCSP response\n");
- goto supply_response;
- }
- }
+ if (running_in_test_harness)
+ {
+ extern char ** environ;
+ uschar ** p;
+ for (p = USS environ; *p != NULL; p++)
+ if (Ustrncmp(*p, "EXIM_TESTHARNESS_DISABLE_OCSPVALIDITYCHECK", 42) == 0)
+ {
+ DEBUG(D_tls) debug_printf("Supplying known bad OCSP response\n");
+ goto supply_response;
+ }
+ }
return;
}
-#endif /*EXPERIMENTAL_OCSP*/
+#endif /*!DISABLE_OCSP*/
"SSL_CTX_use_PrivateKey_file file=%s", expanded), cbinfo->host, NULL);
}
-#ifdef EXPERIMENTAL_OCSP
+#ifndef DISABLE_OCSP
if (cbinfo->is_server && cbinfo->u_ocsp.server.file != NULL)
{
if (!expand_check(cbinfo->u_ocsp.server.file, US"tls_ocsp_file", &expanded))
not confident that memcpy wouldn't break some internal reference counting.
Especially since there's a references struct member, which would be off. */
-server_sni = SSL_CTX_new(SSLv23_server_method());
-if (!server_sni)
+if (!(server_sni = SSL_CTX_new(SSLv23_server_method())))
{
ERR_error_string(ERR_get_error(), ssl_errstring);
DEBUG(D_tls) debug_printf("SSL_CTX_new() failed: %s\n", ssl_errstring);
SSL_CTX_set_tlsext_servername_arg(server_sni, cbinfo);
if (cbinfo->server_cipher_list)
SSL_CTX_set_cipher_list(server_sni, CS cbinfo->server_cipher_list);
-#ifdef EXPERIMENTAL_OCSP
+#ifndef DISABLE_OCSP
if (cbinfo->u_ocsp.server.file)
{
SSL_CTX_set_tlsext_status_cb(server_sni, tls_server_stapling_cb);
rc = tls_expand_session_files(server_sni, cbinfo);
if (rc != OK) return SSL_TLSEXT_ERR_NOACK;
-rc = init_dh(server_sni, cbinfo->dhparam, NULL);
-if (rc != OK) return SSL_TLSEXT_ERR_NOACK;
+if (!init_dh(server_sni, cbinfo->dhparam, NULL))
+ return SSL_TLSEXT_ERR_NOACK;
DEBUG(D_tls) debug_printf("Switching SSL context.\n");
SSL_set_SSL_CTX(s, server_sni);
-#ifdef EXPERIMENTAL_OCSP
+#ifndef DISABLE_OCSP
/*************************************************
* Callback to handle OCSP Stapling *
uschar *response_der;
int response_der_len;
-if (log_extra_selector & LX_tls_cipher)
- log_write(0, LOG_MAIN, "[%s] Recieved OCSP stapling req;%s responding",
- sender_host_address, cbinfo->u_ocsp.server.response ? "":" not");
-else
- DEBUG(D_tls) debug_printf("Received TLS status request (OCSP stapling); %s response.",
+DEBUG(D_tls)
+ debug_printf("Received TLS status request (OCSP stapling); %s response.",
cbinfo->u_ocsp.server.response ? "have" : "lack");
+tls_in.ocsp = OCSP_NOT_RESP;
if (!cbinfo->u_ocsp.server.response)
return SSL_TLSEXT_ERR_NOACK;
response_der = NULL;
-response_der_len = i2d_OCSP_RESPONSE(cbinfo->u_ocsp.server.response, &response_der);
+response_der_len = i2d_OCSP_RESPONSE(cbinfo->u_ocsp.server.response,
+ &response_der);
if (response_der_len <= 0)
return SSL_TLSEXT_ERR_NOACK;
SSL_set_tlsext_status_ocsp_resp(server_ssl, response_der, response_der_len);
+tls_in.ocsp = OCSP_VFIED;
return SSL_TLSEXT_ERR_OK;
}
len = SSL_get_tlsext_status_ocsp_resp(s, &p);
if(!p)
{
- if (log_extra_selector & LX_tls_cipher)
- log_write(0, LOG_MAIN, "Received TLS status response, null content");
+ /* Expect this when we requested ocsp but got none */
+ if ( cbinfo->u_ocsp.client.verify_required
+ && log_extra_selector & LX_tls_cipher)
+ log_write(0, LOG_MAIN, "Received TLS status callback, null content");
else
DEBUG(D_tls) debug_printf(" null\n");
- return 0; /* This is the fail case for require-ocsp; none from server */
+ return cbinfo->u_ocsp.client.verify_required ? 0 : 1;
}
+
if(!(rsp = d2i_OCSP_RESPONSE(NULL, &p, len)))
{
+ tls_out.ocsp = OCSP_FAILED;
if (log_extra_selector & LX_tls_cipher)
- log_write(0, LOG_MAIN, "Received TLS status response, parse error");
+ log_write(0, LOG_MAIN, "Received TLS cert status response, parse error");
else
DEBUG(D_tls) debug_printf(" parse error\n");
return 0;
if(!(bs = OCSP_response_get1_basic(rsp)))
{
+ tls_out.ocsp = OCSP_FAILED;
if (log_extra_selector & LX_tls_cipher)
- log_write(0, LOG_MAIN, "Received TLS status response, error parsing response");
+ log_write(0, LOG_MAIN, "Received TLS cert status response, error parsing response");
else
DEBUG(D_tls) debug_printf(" error parsing response\n");
OCSP_RESPONSE_free(rsp);
/* We'd check the nonce here if we'd put one in the request. */
/* However that would defeat cacheability on the server so we don't. */
-
/* This section of code reworked from OpenSSL apps source;
The OpenSSL Project retains copyright:
Copyright (c) 1999 The OpenSSL Project. All rights reserved.
*/
{
BIO * bp = NULL;
- OCSP_CERTID *id;
int status, reason;
ASN1_GENERALIZEDTIME *rev, *thisupd, *nextupd;
/* Use the chain that verified the server cert to verify the stapled info */
/* DEBUG(D_tls) x509_store_dump_cert_s_names(cbinfo->u_ocsp.client.verify_store); */
- if ((i = OCSP_basic_verify(bs, NULL, cbinfo->u_ocsp.client.verify_store, 0)) <= 0)
+ if ((i = OCSP_basic_verify(bs, NULL,
+ cbinfo->u_ocsp.client.verify_store, 0)) <= 0)
{
+ tls_out.ocsp = OCSP_FAILED;
+ if (log_extra_selector & LX_tls_cipher)
+ log_write(0, LOG_MAIN, "Received TLS cert status response, itself unverifiable");
BIO_printf(bp, "OCSP response verify failure\n");
ERR_print_errors(bp);
- i = 0;
+ i = cbinfo->u_ocsp.client.verify_required ? 0 : 1;
goto out;
}
if (sk_OCSP_SINGLERESP_num(sresp) != 1)
{
- log_write(0, LOG_MAIN, "OCSP stapling with multiple responses not handled");
+ tls_out.ocsp = OCSP_FAILED;
+ log_write(0, LOG_MAIN, "OCSP stapling "
+ "with multiple responses not handled");
+ i = cbinfo->u_ocsp.client.verify_required ? 0 : 1;
goto out;
}
single = OCSP_resp_get0(bs, 0);
- status = OCSP_single_get0_status(single, &reason, &rev, &thisupd, &nextupd);
+ status = OCSP_single_get0_status(single, &reason, &rev,
+ &thisupd, &nextupd);
}
- i = 0;
DEBUG(D_tls) time_print(bp, "This OCSP Update", thisupd);
DEBUG(D_tls) if(nextupd) time_print(bp, "Next OCSP Update", nextupd);
- if (!OCSP_check_validity(thisupd, nextupd, EXIM_OCSP_SKEW_SECONDS, EXIM_OCSP_MAX_AGE))
+ if (!OCSP_check_validity(thisupd, nextupd,
+ EXIM_OCSP_SKEW_SECONDS, EXIM_OCSP_MAX_AGE))
{
+ tls_out.ocsp = OCSP_FAILED;
DEBUG(D_tls) ERR_print_errors(bp);
log_write(0, LOG_MAIN, "Server OSCP dates invalid");
- goto out;
+ i = cbinfo->u_ocsp.client.verify_required ? 0 : 1;
}
-
- DEBUG(D_tls) BIO_printf(bp, "Certificate status: %s\n", OCSP_cert_status_str(status));
- switch(status)
+ else
{
- case V_OCSP_CERTSTATUS_GOOD:
- i = 1;
- break;
- case V_OCSP_CERTSTATUS_REVOKED:
- log_write(0, LOG_MAIN, "Server certificate revoked%s%s",
- reason != -1 ? "; reason: " : "", reason != -1 ? OCSP_crl_reason_str(reason) : "");
- DEBUG(D_tls) time_print(bp, "Revocation Time", rev);
- i = 0;
- break;
- default:
- log_write(0, LOG_MAIN, "Server certificate status unknown, in OCSP stapling");
- i = 0;
- break;
+ DEBUG(D_tls) BIO_printf(bp, "Certificate status: %s\n",
+ OCSP_cert_status_str(status));
+ switch(status)
+ {
+ case V_OCSP_CERTSTATUS_GOOD:
+ tls_out.ocsp = OCSP_VFIED;
+ i = 1;
+ break;
+ case V_OCSP_CERTSTATUS_REVOKED:
+ tls_out.ocsp = OCSP_FAILED;
+ log_write(0, LOG_MAIN, "Server certificate revoked%s%s",
+ reason != -1 ? "; reason: " : "",
+ reason != -1 ? OCSP_crl_reason_str(reason) : "");
+ DEBUG(D_tls) time_print(bp, "Revocation Time", rev);
+ i = cbinfo->u_ocsp.client.verify_required ? 0 : 1;
+ break;
+ default:
+ tls_out.ocsp = OCSP_FAILED;
+ log_write(0, LOG_MAIN,
+ "Server certificate status unknown, in OCSP stapling");
+ i = cbinfo->u_ocsp.client.verify_required ? 0 : 1;
+ break;
+ }
}
out:
BIO_free(bp);
OCSP_RESPONSE_free(rsp);
return i;
}
-#endif /*EXPERIMENTAL_OCSP*/
-
+#endif /*!DISABLE_OCSP*/
/*************************************************
* Initialize for TLS *
*************************************************/
-/* Called from both server and client code, to do preliminary initialization of
-the library.
+/* Called from both server and client code, to do preliminary initialization
+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 callback context
Returns: OK/DEFER/FAIL
*/
static int
tls_init(SSL_CTX **ctxp, host_item *host, uschar *dhparam, uschar *certificate,
uschar *privatekey,
-#ifdef EXPERIMENTAL_OCSP
+#ifndef DISABLE_OCSP
uschar *ocsp_file,
#endif
address_item *addr, tls_ext_ctx_cb ** cbp)
long init_options;
int rc;
BOOL okay;
-tls_ext_ctx_cb *cbinfo;
+tls_ext_ctx_cb * cbinfo;
cbinfo = store_malloc(sizeof(tls_ext_ctx_cb));
cbinfo->certificate = certificate;
cbinfo->privatekey = privatekey;
-#ifdef EXPERIMENTAL_OCSP
+#ifndef DISABLE_OCSP
if ((cbinfo->is_server = host==NULL))
{
cbinfo->u_ocsp.server.file = ocsp_file;
cbinfo->u_ocsp.client.verify_store = NULL;
#endif
cbinfo->dhparam = dhparam;
+cbinfo->server_cipher_list = NULL;
cbinfo->host = host;
+#ifdef EXPERIMENTAL_EVENT
+cbinfo->event_action = NULL;
+#endif
SSL_load_error_strings(); /* basic set up */
OpenSSL_add_ssl_algorithms();
/* Set up the information callback, which outputs if debugging is at a suitable
level. */
-SSL_CTX_set_info_callback(*ctxp, (void (*)())info_callback);
+DEBUG(D_tls) SSL_CTX_set_info_callback(*ctxp, (void (*)())info_callback);
/* Automatically re-try reads/writes after renegotiation. */
(void) SSL_CTX_set_mode(*ctxp, SSL_MODE_AUTO_RETRY);
#ifdef EXIM_HAVE_OPENSSL_TLSEXT
if (host == NULL) /* server */
{
-# ifdef EXPERIMENTAL_OCSP
+# ifndef DISABLE_OCSP
/* We check u_ocsp.server.file, not server.response, because we care about if
the option exists, not what the current expansion might be, as SNI might
change the certificate and OCSP file in use between now and the time the
SSL_CTX_set_tlsext_servername_callback(*ctxp, tls_servername_cb);
SSL_CTX_set_tlsext_servername_arg(*ctxp, cbinfo);
}
-# ifdef EXPERIMENTAL_OCSP
+# ifndef DISABLE_OCSP
else /* client */
if(ocsp_file) /* wanting stapling */
{
# endif
#endif
+cbinfo->verify_cert_hostnames = NULL;
+
/* Set up the RSA callback */
SSL_CTX_set_tmp_rsa_callback(*ctxp, rsa_callback);
}
+static void
+peer_cert(SSL * ssl, tls_support * tlsp, uschar * peerdn, unsigned bsize)
+{
+/*XXX we might consider a list-of-certs variable for the cert chain.
+SSL_get_peer_cert_chain(SSL*). We'd need a new variable type and support
+in list-handling functions, also consider the difference between the entire
+chain and the elements sent by the peer. */
+
+/* Will have already noted peercert on a verify fail; possibly not the leaf */
+if (!tlsp->peercert)
+ tlsp->peercert = SSL_get_peer_certificate(ssl);
+/* Beware anonymous ciphers which lead to server_cert being NULL */
+if (tlsp->peercert)
+ {
+ X509_NAME_oneline(X509_get_subject_name(tlsp->peercert), CS peerdn, bsize);
+ peerdn[bsize-1] = '\0';
+ tlsp->peerdn = peerdn; /*XXX a static buffer... */
+ }
+else
+ tlsp->peerdn = NULL;
+}
+
+
if (expcerts != NULL && *expcerts != '\0')
{
- struct stat statbuf;
- if (!SSL_CTX_set_default_verify_paths(sctx))
- return tls_error(US"SSL_CTX_set_default_verify_paths", host, NULL);
-
- if (Ustat(expcerts, &statbuf) < 0)
+ if (Ustrcmp(expcerts, "system") == 0)
{
- log_write(0, LOG_MAIN|LOG_PANIC,
- "failed to stat %s for certificates", expcerts);
- return DEFER;
+ /* Tell the library to use its compiled-in location for the system default
+ CA bundle, only */
+
+ if (!SSL_CTX_set_default_verify_paths(sctx))
+ return tls_error(US"SSL_CTX_set_default_verify_paths", host, NULL);
}
else
{
- uschar *file, *dir;
- if ((statbuf.st_mode & S_IFMT) == S_IFDIR)
- { file = NULL; dir = expcerts; }
- else
- { file = expcerts; dir = NULL; }
+ struct stat statbuf;
- /* If a certificate file is empty, the next function fails with an
- unhelpful error message. If we skip it, we get the correct behaviour (no
- certificates are recognized, but the error message is still misleading (it
- says no certificate was supplied.) But this is better. */
+ /* Tell the library to use its compiled-in location for the system default
+ CA bundle. Those given by the exim config are additional to these */
- if ((file == NULL || statbuf.st_size > 0) &&
- !SSL_CTX_load_verify_locations(sctx, CS file, CS dir))
- return tls_error(US"SSL_CTX_load_verify_locations", host, NULL);
+ if (!SSL_CTX_set_default_verify_paths(sctx))
+ return tls_error(US"SSL_CTX_set_default_verify_paths", host, NULL);
- if (file != NULL)
+ if (Ustat(expcerts, &statbuf) < 0)
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "failed to stat %s for certificates", expcerts);
+ return DEFER;
+ }
+ else
{
- SSL_CTX_set_client_CA_list(sctx, SSL_load_client_CA_file(CS file));
+ uschar *file, *dir;
+ if ((statbuf.st_mode & S_IFMT) == S_IFDIR)
+ { file = NULL; dir = expcerts; }
+ else
+ { file = expcerts; dir = NULL; }
+
+ /* If a certificate file is empty, the next function fails with an
+ unhelpful error message. If we skip it, we get the correct behaviour (no
+ certificates are recognized, but the error message is still misleading (it
+ says no certificate was supplied.) But this is better. */
+
+ if ((file == NULL || statbuf.st_size > 0) &&
+ !SSL_CTX_load_verify_locations(sctx, CS file, CS dir))
+ return tls_error(US"SSL_CTX_load_verify_locations", host, NULL);
+
+ /* Load the list of CAs for which we will accept certs, for sending
+ to the client. This is only for the one-file tls_verify_certificates
+ variant.
+ If a list isn't loaded into the server, but
+ some verify locations are set, the server end appears to make
+ a wildcard reqest for client certs.
+ Meanwhile, the client library as deafult behaviour *ignores* the list
+ we send over the wire - see man SSL_CTX_set_client_cert_cb.
+ Because of this, and that the dir variant is likely only used for
+ the public-CA bundle (not for a private CA), not worth fixing.
+ */
+ if (file != NULL)
+ {
+ STACK_OF(X509_NAME) * names = SSL_load_client_CA_file(CS file);
+ DEBUG(D_tls) debug_printf("Added %d certificate authorities.\n",
+ sk_X509_NAME_num(names));
+ SSL_CTX_set_client_CA_list(sctx, names);
+ }
}
}
int rc;
uschar *expciphers;
tls_ext_ctx_cb *cbinfo;
+X509 * peercert;
+static uschar peerdn[256];
static uschar cipherbuf[256];
/* Check for previous activation */
the error. */
rc = tls_init(&server_ctx, NULL, tls_dhparam, tls_certificate, tls_privatekey,
-#ifdef EXPERIMENTAL_OCSP
+#ifndef DISABLE_OCSP
tls_ocsp_file,
#endif
NULL, &server_static_cbinfo);
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)
/* TLS has been set up. Adjust the input functions to read via TLS,
and initialize things. */
+peer_cert(server_ssl, &tls_in, peerdn, sizeof(peerdn));
+
construct_cipher_name(server_ssl, cipherbuf, sizeof(cipherbuf), &tls_in.bits);
tls_in.cipher = cipherbuf;
debug_printf("Shared ciphers: %s\n", buf);
}
+/* Record the certificate we presented */
+ {
+ X509 * crt = SSL_get_certificate(server_ssl);
+ tls_in.ourcert = crt ? X509_dup(crt) : NULL;
+ }
/* Only used by the server-side tls (tls_in), including tls_getc.
Client-side (tls_out) reads (seem to?) go via
+static int
+tls_client_basic_ctx_init(SSL_CTX * ctx,
+ host_item * host, smtp_transport_options_block * ob, tls_ext_ctx_cb * cbinfo
+ )
+{
+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 || !*ob->tls_try_verify_hosts)
+ )
+ || (verify_check_given_host(&ob->tls_verify_hosts, host) == OK)
+ )
+ client_verify_optional = FALSE;
+else if (verify_check_given_host(&ob->tls_try_verify_hosts, host) == OK)
+ client_verify_optional = TRUE;
+else
+ return OK;
+
+if ((rc = setup_certs(ctx, ob->tls_verify_certificates,
+ ob->tls_crl, host, client_verify_optional, verify_callback_client)) != OK)
+ return rc;
+
+if (verify_check_given_host(&ob->tls_verify_cert_hostnames, host) == OK)
+ {
+ cbinfo->verify_cert_hostnames = host->name;
+ DEBUG(D_tls) debug_printf("Cert hostname to check: \"%s\"\n",
+ cbinfo->verify_cert_hostnames);
+ }
+return OK;
+}
+
+
+#ifdef EXPERIMENTAL_DANE
+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;
+
+ usage = *p++;
+
+ /* Only DANE-TA(2) and DANE-EE(3) are supported */
+ if (usage != 2 && usage != 3) continue;
+
+ selector = *p++;
+ mtype = *p++;
+
+ switch (mtype)
+ {
+ default: continue; /* Only match-types 0, 1, 2 are supported */
+ case 0: mdname = NULL; break;
+ case 1: mdname = "sha256"; break;
+ case 2: mdname = "sha512"; break;
+ }
+
+ found++;
+ 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 usable TLSA records");
+return DEFER;
+}
+#endif /*EXPERIMENTAL_DANE*/
+
+
/*************************************************
* Start a TLS session in a client *
fd the fd of the connection
host connected host (for messages)
addr the first address
- certificate certificate file
- privatekey private key file
- sni TLS SNI to send to remote host
- verify_certs file for certificate verify
- crl file containing CRL
- require_ciphers list of allowed ciphers
- dh_min_bits minimum number of bits acceptable in server's DH prime
- (unused in OpenSSL)
- timeout startup timeout
- verify_hosts mandatory client verification
- try_verify_hosts optional client verification
+ tb transport (always smtp)
+ tlsa_dnsa tlsa lookup, if DANE, else null
Returns: OK on success
FAIL otherwise - note that tls_error() will not give DEFER
int
tls_client_start(int fd, host_item *host, address_item *addr,
- uschar *certificate, uschar *privatekey, uschar *sni,
- uschar *verify_certs, uschar *crl,
- uschar *require_ciphers,
-#ifdef EXPERIMENTAL_OCSP
- uschar *hosts_require_ocsp,
-#endif
- int dh_min_bits ARG_UNUSED, int timeout,
- uschar *verify_hosts, uschar *try_verify_hosts)
+ transport_instance *tb
+#ifdef EXPERIMENTAL_DANE
+ , dns_answer * tlsa_dnsa
+#endif
+ )
{
-static uschar txt[256];
-uschar *expciphers;
-X509* server_cert;
+smtp_transport_options_block * ob =
+ (smtp_transport_options_block *)tb->options_block;
+static uschar peerdn[256];
+uschar * expciphers;
int rc;
static uschar cipherbuf[256];
-#ifdef EXPERIMENTAL_OCSP
-BOOL require_ocsp = verify_check_this_host(&hosts_require_ocsp,
- NULL, host->name, host->address, NULL) == OK;
+
+#ifndef DISABLE_OCSP
+BOOL request_ocsp = FALSE;
+BOOL require_ocsp = FALSE;
#endif
-rc = tls_init(&client_ctx, host, NULL, certificate, privatekey,
-#ifdef EXPERIMENTAL_OCSP
- require_ocsp ? US"" : NULL,
+#ifdef EXPERIMENTAL_DANE
+tls_out.tlsa_usage = 0;
+#endif
+
+#ifndef DISABLE_OCSP
+ {
+# ifdef EXPERIMENTAL_DANE
+ if ( tlsa_dnsa
+ && 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}} } "
+ " {*}{}}";
+ }
+# endif
+
+ if ((require_ocsp =
+ verify_check_given_host(&ob->hosts_require_ocsp, host) == OK))
+ request_ocsp = TRUE;
+ else
+# ifdef EXPERIMENTAL_DANE
+ if (!request_ocsp)
+# endif
+ request_ocsp =
+ verify_check_given_host(&ob->hosts_request_ocsp, host) == OK;
+ }
+#endif
+
+rc = tls_init(&client_ctx, host, NULL,
+ ob->tls_certificate, ob->tls_privatekey,
+#ifndef DISABLE_OCSP
+ (void *)(long)request_ocsp,
#endif
addr, &client_static_cbinfo);
if (rc != OK) return rc;
tls_out.certificate_verified = FALSE;
client_verify_callback_called = FALSE;
-if (!expand_check(require_ciphers, US"tls_require_ciphers", &expciphers))
+if (!expand_check(ob->tls_require_ciphers, US"tls_require_ciphers",
+ &expciphers))
return FAIL;
/* In OpenSSL, cipher components are separated by hyphens. In GnuTLS, they
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 (((verify_hosts == NULL) && (try_verify_hosts == NULL)) ||
- (verify_check_host(&verify_hosts) == OK))
- {
- rc = setup_certs(client_ctx, verify_certs, crl, host, FALSE, verify_callback_client);
- if (rc != OK) return rc;
- client_verify_optional = FALSE;
- }
-else if (verify_check_host(&try_verify_hosts) == OK)
+#ifdef EXPERIMENTAL_DANE
+if (tlsa_dnsa)
{
- rc = setup_certs(client_ctx, verify_certs, crl, host, TRUE, verify_callback_client);
- if (rc != OK) return rc;
- client_verify_optional = TRUE;
+ SSL_CTX_set_verify(client_ctx,
+ SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
+ verify_callback_client_dane);
+
+ if (!DANESSL_library_init())
+ return tls_error(US"library init", host, NULL);
+ if (DANESSL_CTX_init(client_ctx) <= 0)
+ return tls_error(US"context init", host, NULL);
}
+else
-if ((client_ssl = SSL_new(client_ctx)) == NULL) return tls_error(US"SSL_new", host, NULL);
+#endif
+
+ if ((rc = tls_client_basic_ctx_init(client_ctx, host, ob, client_static_cbinfo))
+ != OK)
+ return rc;
+
+if ((client_ssl = SSL_new(client_ctx)) == NULL)
+ return tls_error(US"SSL_new", host, NULL);
SSL_set_session_id_context(client_ssl, sid_ctx, Ustrlen(sid_ctx));
SSL_set_fd(client_ssl, fd);
SSL_set_connect_state(client_ssl);
-if (sni)
+if (ob->tls_sni)
{
- if (!expand_check(sni, US"tls_sni", &tls_out.sni))
+ if (!expand_check(ob->tls_sni, US"tls_sni", &tls_out.sni))
return FAIL;
if (tls_out.sni == NULL)
{
}
}
-#ifdef EXPERIMENTAL_OCSP
+#ifdef EXPERIMENTAL_DANE
+if (tlsa_dnsa)
+ 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 (require_ocsp)
+# ifdef EXPERIMENTAL_DANE
+if (request_ocsp)
+ {
+ const uschar * s;
+ if ( ((s = ob->hosts_require_ocsp) && Ustrstr(s, US"tls_out_tlsa_usage"))
+ || ((s = ob->hosts_request_ocsp) && Ustrstr(s, US"tls_out_tlsa_usage"))
+ )
+ { /* Re-eval now $tls_out_tlsa_usage is populated. If
+ this means we avoid the OCSP request, we wasted the setup
+ cost in tls_init(). */
+ require_ocsp = verify_check_given_host(&ob->hosts_require_ocsp, host) == OK;
+ request_ocsp = require_ocsp
+ || verify_check_given_host(&ob->hosts_request_ocsp, host) == OK;
+ }
+ }
+# endif
+
+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;
+ }
+#endif
+
+#ifdef EXPERIMENTAL_EVENT
+client_static_cbinfo->event_action = tb->event_action;
#endif
/* There doesn't seem to be a built-in timeout on connection. */
DEBUG(D_tls) debug_printf("Calling SSL_connect\n");
sigalrm_seen = FALSE;
-alarm(timeout);
+alarm(ob->command_timeout);
rc = SSL_connect(client_ssl);
alarm(0);
+#ifdef EXPERIMENTAL_DANE
+if (tlsa_dnsa)
+ DANESSL_cleanup(client_ssl);
+#endif
+
if (rc <= 0)
return tls_error(US"SSL_connect", host, sigalrm_seen ? US"timed out" : NULL);
DEBUG(D_tls) debug_printf("SSL_connect succeeded\n");
-/* Beware anonymous ciphers which lead to server_cert being NULL */
-server_cert = SSL_get_peer_certificate (client_ssl);
-if (server_cert)
- {
- tls_out.peerdn = US X509_NAME_oneline(X509_get_subject_name(server_cert),
- CS txt, sizeof(txt));
- tls_out.peerdn = txt;
- }
-else
- tls_out.peerdn = NULL;
+peer_cert(client_ssl, &tls_out, peerdn, sizeof(peerdn));
construct_cipher_name(client_ssl, cipherbuf, sizeof(cipherbuf), &tls_out.bits);
tls_out.cipher = cipherbuf;
+/* Record the certificate we presented */
+ {
+ X509 * crt = SSL_get_certificate(client_ssl);
+ tls_out.ourcert = crt ? X509_dup(crt) : NULL;
+ }
+
tls_out.active = fd;
return OK;
}
return TRUE;
}
+/* vi: aw ai sw=2
+*/
/* End of tls-openssl.c */