* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2014 */
/* See the file NOTICE for conditions of use and distribution. */
+/* Portions Copyright (c) The OpenSSL Project 1999 */
+
/* This module provides the TLS (aka SSL) support for Exim using the OpenSSL
library. It is #included into the tls.c file when that library is used. The
code herein is based on a patch that was originally contributed by Steve
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/rand.h>
+#ifdef EXPERIMENTAL_OCSP
+#include <openssl/ocsp.h>
+#endif
+
+#ifdef EXPERIMENTAL_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
+#endif
/* Structure for collecting random data for seeding. */
/* Local static variables */
-static BOOL verify_callback_called = FALSE;
+static BOOL client_verify_callback_called = FALSE;
+static BOOL server_verify_callback_called = FALSE;
static const uschar *sid_ctx = US"exim";
-static SSL_CTX *ctx = NULL;
-static SSL_CTX *ctx_sni = NULL;
-static SSL *ssl = NULL;
+/* We have three different contexts to care about.
+
+Simple case: client, `client_ctx`
+ As a client, we can be doing a callout or cut-through delivery while receiving
+ a message. So we have a client context, which should have options initialised
+ from the SMTP Transport.
+
+Server:
+ There are two cases: with and without ServerNameIndication from the client.
+ Given TLS SNI, we can be using different keys, certs and various other
+ configuration settings, because they're re-expanded with $tls_sni set. This
+ allows vhosting with TLS. This SNI is sent in the handshake.
+ A client might not send SNI, so we need a fallback, and an initial setup too.
+ So as a server, we start out using `server_ctx`.
+ If SNI is sent by the client, then we as server, mid-negotiation, try to clone
+ `server_sni` from `server_ctx` and then initialise settings by re-expanding
+ configuration.
+*/
+
+static SSL_CTX *client_ctx = NULL;
+static SSL_CTX *server_ctx = NULL;
+static SSL *client_ssl = NULL;
+static SSL *server_ssl = NULL;
+
+#ifdef EXIM_HAVE_OPENSSL_TLSEXT
+static SSL_CTX *server_sni = NULL;
+#endif
static char ssl_errstring[256];
static int ssl_session_timeout = 200;
-static BOOL verify_optional = FALSE;
+static BOOL client_verify_optional = FALSE;
+static BOOL server_verify_optional = FALSE;
-static BOOL reexpand_tls_files_for_sni = FALSE;
+static BOOL reexpand_tls_files_for_sni = FALSE;
typedef struct tls_ext_ctx_cb {
uschar *certificate;
uschar *privatekey;
+#ifdef EXPERIMENTAL_OCSP
+ BOOL is_server;
+ union {
+ struct {
+ uschar *file;
+ uschar *file_expanded;
+ OCSP_RESPONSE *response;
+ } server;
+ struct {
+ X509_STORE *verify_store; /* non-null if status requested */
+ BOOL verify_required;
+ } client;
+ } u_ocsp;
+#endif
uschar *dhparam;
/* these are cached from first expand */
uschar *server_cipher_list;
/* should figure out a cleanup of API to handle state preserved per
implementation, for various reasons, which can be void * in the APIs.
For now, we hack around it. */
-tls_ext_ctx_cb *static_cbinfo = NULL;
+tls_ext_ctx_cb *client_static_cbinfo = NULL;
+tls_ext_ctx_cb *server_static_cbinfo = NULL;
static int
-setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional);
+setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional,
+ int (*cert_vfy_cb)(int, X509_STORE_CTX *) );
+
+/* Callbacks */
+#ifdef EXIM_HAVE_OPENSSL_TLSEXT
+static int tls_servername_cb(SSL *s, int *ad ARG_UNUSED, void *arg);
+#endif
+#ifdef EXPERIMENTAL_OCSP
+static int tls_server_stapling_cb(SSL *s, void *arg);
+#endif
/*************************************************
+/* Extreme debug
+#if defined(EXPERIMENTAL_OCSP)
+void
+x509_store_dump_cert_s_names(X509_STORE * store)
+{
+STACK_OF(X509_OBJECT) * roots= store->objs;
+int i;
+static uschar name[256];
+
+for(i= 0; i<sk_X509_OBJECT_num(roots); i++)
+ {
+ X509_OBJECT * tmp_obj= sk_X509_OBJECT_value(roots, i);
+ if(tmp_obj->type == X509_LU_X509)
+ {
+ X509 * current_cert= tmp_obj->data.x509;
+ X509_NAME_oneline(X509_get_subject_name(current_cert), CS name, sizeof(name));
+ debug_printf(" %s\n", name);
+ }
+ }
+}
+#endif
+*/
+
/*************************************************
* Callback for verification *
Arguments:
state current yes/no state as 1/0
x509ctx certificate information.
+ client TRUE for client startup, FALSE for server startup
Returns: 1 if verified, 0 if not
*/
static int
-verify_callback(int state, X509_STORE_CTX *x509ctx)
+verify_callback(int state, X509_STORE_CTX *x509ctx, tls_support *tlsp, BOOL *calledp, BOOL *optionalp)
{
static uschar txt[256];
x509ctx->error_depth,
X509_verify_cert_error_string(x509ctx->error),
txt);
- tls_certificate_verified = FALSE;
- verify_callback_called = TRUE;
- if (!verify_optional) return 0; /* reject */
+ tlsp->certificate_verified = FALSE;
+ *calledp = TRUE;
+ if (!*optionalp)
+ {
+ tlsp->peercert = X509_dup(x509ctx->current_cert);
+ return 0; /* reject */
+ }
DEBUG(D_tls) debug_printf("SSL verify failure overridden (host in "
"tls_try_verify_hosts)\n");
return 1; /* accept */
{
DEBUG(D_tls) debug_printf("SSL verify ok: depth=%d cert=%s\n",
x509ctx->error_depth, txt);
+#ifdef EXPERIMENTAL_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))
+ ERR_clear_error();
+ }
+#endif
}
else
{
DEBUG(D_tls) debug_printf("SSL%s peer: %s\n",
- verify_callback_called? "" : " authenticated", txt);
- tls_peerdn = txt;
+ *calledp ? "" : " authenticated", txt);
+ tlsp->peerdn = txt;
+ tlsp->peercert = X509_dup(x509ctx->current_cert);
}
-if (!verify_callback_called) tls_certificate_verified = TRUE;
-verify_callback_called = TRUE;
+/*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?
+
+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;
return 1; /* accept */
}
+static int
+verify_callback_client(int state, X509_STORE_CTX *x509ctx)
+{
+return verify_callback(state, x509ctx, &tls_out, &client_verify_callback_called, &client_verify_optional);
+}
+
+static int
+verify_callback_server(int state, X509_STORE_CTX *x509ctx)
+{
+return verify_callback(state, x509ctx, &tls_in, &server_verify_callback_called, &server_verify_optional);
+}
+
/*************************************************
/* If dhparam is set, expand it, and load up the parameters for DH encryption.
Arguments:
- dhparam DH parameter file
+ dhparam DH parameter file or fixed parameter identity string
host connected host, if client; NULL if server
Returns: TRUE if OK (nothing to set up, or setup worked)
*/
static BOOL
-init_dh(uschar *dhparam, host_item *host)
+init_dh(SSL_CTX *sctx, uschar *dhparam, host_item *host)
{
-BOOL yield = TRUE;
BIO *bio;
DH *dh;
uschar *dhexpanded;
+const char *pem;
if (!expand_check(dhparam, US"tls_dhparam", &dhexpanded))
return FALSE;
-if (dhexpanded == NULL) return TRUE;
-
-if ((bio = BIO_new_file(CS dhexpanded, "r")) == NULL)
+if (dhexpanded == NULL || *dhexpanded == '\0')
{
- tls_error(string_sprintf("could not read dhparams file %s", dhexpanded),
- host, (uschar *)strerror(errno));
- yield = FALSE;
+ bio = BIO_new_mem_buf(CS std_dh_prime_default(), -1);
}
-else
+else if (dhexpanded[0] == '/')
{
- if ((dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL)) == NULL)
+ bio = BIO_new_file(CS dhexpanded, "r");
+ if (bio == NULL)
{
tls_error(string_sprintf("could not read dhparams file %s", dhexpanded),
- host, NULL);
- yield = FALSE;
+ host, US strerror(errno));
+ return FALSE;
}
- else
+ }
+else
+ {
+ if (Ustrcmp(dhexpanded, "none") == 0)
{
- SSL_CTX_set_tmp_dh(ctx, dh);
- DEBUG(D_tls)
- debug_printf("Diffie-Hellman initialized from %s with %d-bit key\n",
- dhexpanded, 8*DH_size(dh));
- DH_free(dh);
+ DEBUG(D_tls) debug_printf("Requested no DH parameters.\n");
+ return TRUE;
+ }
+
+ pem = std_dh_prime_named(dhexpanded);
+ if (!pem)
+ {
+ tls_error(string_sprintf("Unknown standard DH prime \"%s\"", dhexpanded),
+ host, US strerror(errno));
+ return FALSE;
}
+ bio = BIO_new_mem_buf(CS pem, -1);
+ }
+
+dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
+if (dh == NULL)
+ {
BIO_free(bio);
+ tls_error(string_sprintf("Could not read tls_dhparams \"%s\"", dhexpanded),
+ host, NULL);
+ return FALSE;
+ }
+
+/* Even if it is larger, we silently return success rather than cause things
+ * to fail out, so that a too-large DH will not knock out all TLS; it's a
+ * debatable choice. */
+if ((8*DH_size(dh)) > tls_dh_max_bits)
+ {
+ DEBUG(D_tls)
+ debug_printf("dhparams file %d bits, is > tls_dh_max_bits limit of %d",
+ 8*DH_size(dh), tls_dh_max_bits);
}
+else
+ {
+ SSL_CTX_set_tmp_dh(sctx, dh);
+ DEBUG(D_tls)
+ debug_printf("Diffie-Hellman initialized from %s with %d-bit prime\n",
+ dhexpanded ? dhexpanded : US"default", 8*DH_size(dh));
+ }
+
+DH_free(dh);
+BIO_free(bio);
-return yield;
+return TRUE;
}
+#ifdef EXPERIMENTAL_OCSP
+/*************************************************
+* Load OCSP information into state *
+*************************************************/
+
+/* Called to load the server OCSP response from the given file into memory, once
+caller has determined this is needed. Checks validity. Debugs a message
+if invalid.
+
+ASSUMES: single response, for single cert.
+
+Arguments:
+ sctx the SSL_CTX* to update
+ cbinfo various parts of session state
+ expanded the filename putatively holding an OCSP response
+
+*/
+
+static void
+ocsp_load_response(SSL_CTX *sctx, tls_ext_ctx_cb *cbinfo, const uschar *expanded)
+{
+BIO *bio;
+OCSP_RESPONSE *resp;
+OCSP_BASICRESP *basic_response;
+OCSP_SINGLERESP *single_response;
+ASN1_GENERALIZEDTIME *rev, *thisupd, *nextupd;
+X509_STORE *store;
+unsigned long verify_flags;
+int status, reason, i;
+
+cbinfo->u_ocsp.server.file_expanded = string_copy(expanded);
+if (cbinfo->u_ocsp.server.response)
+ {
+ OCSP_RESPONSE_free(cbinfo->u_ocsp.server.response);
+ cbinfo->u_ocsp.server.response = NULL;
+ }
+
+bio = BIO_new_file(CS cbinfo->u_ocsp.server.file_expanded, "rb");
+if (!bio)
+ {
+ DEBUG(D_tls) debug_printf("Failed to open OCSP response file \"%s\"\n",
+ cbinfo->u_ocsp.server.file_expanded);
+ return;
+ }
+
+resp = d2i_OCSP_RESPONSE_bio(bio, NULL);
+BIO_free(bio);
+if (!resp)
+ {
+ DEBUG(D_tls) debug_printf("Error reading OCSP response.\n");
+ return;
+ }
+
+status = OCSP_response_status(resp);
+if (status != OCSP_RESPONSE_STATUS_SUCCESSFUL)
+ {
+ DEBUG(D_tls) debug_printf("OCSP response not valid: %s (%d)\n",
+ OCSP_response_status_str(status), status);
+ goto bad;
+ }
+
+basic_response = OCSP_response_get1_basic(resp);
+if (!basic_response)
+ {
+ DEBUG(D_tls)
+ debug_printf("OCSP response parse error: unable to extract basic response.\n");
+ goto bad;
+ }
+
+store = SSL_CTX_get_cert_store(sctx);
+verify_flags = OCSP_NOVERIFY; /* check sigs, but not purpose */
+
+/* May need to expose ability to adjust those flags?
+OCSP_NOSIGS OCSP_NOVERIFY OCSP_NOCHAIN OCSP_NOCHECKS OCSP_NOEXPLICIT
+OCSP_TRUSTOTHER OCSP_NOINTERN */
+
+i = OCSP_basic_verify(basic_response, NULL, store, verify_flags);
+if (i <= 0)
+ {
+ DEBUG(D_tls) {
+ ERR_error_string(ERR_get_error(), ssl_errstring);
+ debug_printf("OCSP response verify failure: %s\n", US ssl_errstring);
+ }
+ goto bad;
+ }
+
+/* Here's the simplifying assumption: there's only one response, for the
+one certificate we use, and nothing for anything else in a chain. If this
+proves false, we need to extract a cert id from our issued cert
+(tls_certificate) and use that for OCSP_resp_find_status() (which finds the
+right cert in the stack and then calls OCSP_single_get0_status()).
+
+I'm hoping to avoid reworking a bunch more of how we handle state here. */
+single_response = OCSP_resp_get0(basic_response, 0);
+if (!single_response)
+ {
+ DEBUG(D_tls)
+ debug_printf("Unable to get first response from OCSP basic response.\n");
+ goto bad;
+ }
+
+status = OCSP_single_get0_status(single_response, &reason, &rev, &thisupd, &nextupd);
+if (status != V_OCSP_CERTSTATUS_GOOD)
+ {
+ DEBUG(D_tls) debug_printf("OCSP response bad cert status: %s (%d) %s (%d)\n",
+ OCSP_cert_status_str(status), status,
+ OCSP_crl_reason_str(reason), reason);
+ goto bad;
+ }
+
+if (!OCSP_check_validity(thisupd, nextupd, EXIM_OCSP_SKEW_SECONDS, EXIM_OCSP_MAX_AGE))
+ {
+ DEBUG(D_tls) debug_printf("OCSP status invalid times.\n");
+ goto bad;
+ }
+
+supply_response:
+ 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;
+ }
+ }
+return;
+}
+#endif /*EXPERIMENTAL_OCSP*/
+
+
+
+
/*************************************************
* Expand key and cert file specs *
*************************************************/
-/* Called once during tls_init and possibly againt during TLS setup, for a
+/* Called once during tls_init and possibly again during TLS setup, for a
new context, if Server Name Indication was used and tls_sni was seen in
the certificate string.
*/
static int
-tls_expand_session_files(SSL_CTX *sctx, const tls_ext_ctx_cb *cbinfo)
+tls_expand_session_files(SSL_CTX *sctx, tls_ext_ctx_cb *cbinfo)
{
uschar *expanded;
if (cbinfo->certificate == NULL)
return OK;
-if (Ustrstr(cbinfo->certificate, US"tls_sni"))
+if (Ustrstr(cbinfo->certificate, US"tls_sni") ||
+ Ustrstr(cbinfo->certificate, US"tls_in_sni") ||
+ Ustrstr(cbinfo->certificate, US"tls_out_sni")
+ )
reexpand_tls_files_for_sni = TRUE;
if (!expand_check(cbinfo->certificate, US"tls_certificate", &expanded))
"SSL_CTX_use_PrivateKey_file file=%s", expanded), cbinfo->host, NULL);
}
+#ifdef EXPERIMENTAL_OCSP
+if (cbinfo->is_server && cbinfo->u_ocsp.server.file != NULL)
+ {
+ if (!expand_check(cbinfo->u_ocsp.server.file, US"tls_ocsp_file", &expanded))
+ return DEFER;
+
+ if (expanded != NULL && *expanded != 0)
+ {
+ DEBUG(D_tls) debug_printf("tls_ocsp_file %s\n", expanded);
+ if (cbinfo->u_ocsp.server.file_expanded &&
+ (Ustrcmp(expanded, cbinfo->u_ocsp.server.file_expanded) == 0))
+ {
+ DEBUG(D_tls)
+ debug_printf("tls_ocsp_file value unchanged, using existing values.\n");
+ } else {
+ ocsp_load_response(sctx, cbinfo, expanded);
+ }
+ }
+ }
+#endif
+
return OK;
}
Returns: SSL_TLSEXT_ERR_{OK,ALERT_WARNING,ALERT_FATAL,NOACK}
*/
-static int
-tls_servername_cb(SSL *s, int *ad ARG_UNUSED, void *arg);
-/* pre-declared for SSL_CTX_set_tlsext_servername_callback call within func */
-
+#ifdef EXIM_HAVE_OPENSSL_TLSEXT
static int
tls_servername_cb(SSL *s, int *ad ARG_UNUSED, void *arg)
{
const char *servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name);
-const tls_ext_ctx_cb *cbinfo = (tls_ext_ctx_cb *) arg;
+tls_ext_ctx_cb *cbinfo = (tls_ext_ctx_cb *) arg;
int rc;
+int old_pool = store_pool;
if (!servername)
return SSL_TLSEXT_ERR_OK;
-DEBUG(D_tls) debug_printf("TLS SNI: %s%s\n", servername,
+DEBUG(D_tls) debug_printf("Received TLS SNI \"%s\"%s\n", servername,
reexpand_tls_files_for_sni ? "" : " (unused for certificate selection)");
/* Make the extension value available for expansion */
-tls_sni = servername;
+store_pool = POOL_PERM;
+tls_in.sni = string_copy(US servername);
+store_pool = old_pool;
if (!reexpand_tls_files_for_sni)
return SSL_TLSEXT_ERR_OK;
not confident that memcpy wouldn't break some internal reference counting.
Especially since there's a references struct member, which would be off. */
-ctx_sni = SSL_CTX_new(SSLv23_server_method());
-if (!ctx_sni)
+server_sni = SSL_CTX_new(SSLv23_server_method());
+if (!server_sni)
{
ERR_error_string(ERR_get_error(), ssl_errstring);
DEBUG(D_tls) debug_printf("SSL_CTX_new() failed: %s\n", ssl_errstring);
/* Not sure how many of these are actually needed, since SSL object
already exists. Might even need this selfsame callback, for reneg? */
-SSL_CTX_set_info_callback(ctx_sni, SSL_CTX_get_info_callback(ctx));
-SSL_CTX_set_mode(ctx_sni, SSL_CTX_get_mode(ctx));
-SSL_CTX_set_options(ctx_sni, SSL_CTX_get_options(ctx));
-SSL_CTX_set_timeout(ctx_sni, SSL_CTX_get_timeout(ctx));
-SSL_CTX_set_tlsext_servername_callback(ctx_sni, tls_servername_cb);
-SSL_CTX_set_tlsext_servername_arg(ctx_sni, cbinfo);
+SSL_CTX_set_info_callback(server_sni, SSL_CTX_get_info_callback(server_ctx));
+SSL_CTX_set_mode(server_sni, SSL_CTX_get_mode(server_ctx));
+SSL_CTX_set_options(server_sni, SSL_CTX_get_options(server_ctx));
+SSL_CTX_set_timeout(server_sni, SSL_CTX_get_timeout(server_ctx));
+SSL_CTX_set_tlsext_servername_callback(server_sni, tls_servername_cb);
+SSL_CTX_set_tlsext_servername_arg(server_sni, cbinfo);
if (cbinfo->server_cipher_list)
- SSL_CTX_set_cipher_list(ctx_sni, CS cbinfo->server_cipher_list);
+ SSL_CTX_set_cipher_list(server_sni, CS cbinfo->server_cipher_list);
+#ifdef EXPERIMENTAL_OCSP
+if (cbinfo->u_ocsp.server.file)
+ {
+ SSL_CTX_set_tlsext_status_cb(server_sni, tls_server_stapling_cb);
+ SSL_CTX_set_tlsext_status_arg(server_sni, cbinfo);
+ }
+#endif
+
+rc = setup_certs(server_sni, tls_verify_certificates, tls_crl, NULL, FALSE, verify_callback_server);
+if (rc != OK) return SSL_TLSEXT_ERR_NOACK;
-rc = tls_expand_session_files(ctx_sni, cbinfo);
+/* do this after setup_certs, because this can require the certs for verifying
+OCSP information. */
+rc = tls_expand_session_files(server_sni, cbinfo);
if (rc != OK) return SSL_TLSEXT_ERR_NOACK;
-rc = setup_certs(ctx_sni, tls_verify_certificates, tls_crl, NULL, FALSE);
+rc = init_dh(server_sni, cbinfo->dhparam, NULL);
if (rc != OK) return SSL_TLSEXT_ERR_NOACK;
DEBUG(D_tls) debug_printf("Switching SSL context.\n");
-SSL_set_SSL_CTX(s, ctx_sni);
+SSL_set_SSL_CTX(s, server_sni);
+
+return SSL_TLSEXT_ERR_OK;
+}
+#endif /* EXIM_HAVE_OPENSSL_TLSEXT */
+
+
+
+
+#ifdef EXPERIMENTAL_OCSP
+
+/*************************************************
+* Callback to handle OCSP Stapling *
+*************************************************/
+
+/* Called when acting as server during the TLS session setup if the client
+requests OCSP information with a Certificate Status Request.
+
+Documentation via openssl s_server.c and the Apache patch from the OpenSSL
+project.
+
+*/
+
+static int
+tls_server_stapling_cb(SSL *s, void *arg)
+{
+const tls_ext_ctx_cb *cbinfo = (tls_ext_ctx_cb *) arg;
+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.",
+ 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);
+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;
}
+static void
+time_print(BIO * bp, const char * str, ASN1_GENERALIZEDTIME * time)
+{
+BIO_printf(bp, "\t%s: ", str);
+ASN1_GENERALIZEDTIME_print(bp, time);
+BIO_puts(bp, "\n");
+}
+
+static int
+tls_client_stapling_cb(SSL *s, void *arg)
+{
+tls_ext_ctx_cb * cbinfo = arg;
+const unsigned char * p;
+int len;
+OCSP_RESPONSE * rsp;
+OCSP_BASICRESP * bs;
+int i;
+
+DEBUG(D_tls) debug_printf("Received TLS status response (OCSP stapling):");
+len = SSL_get_tlsext_status_ocsp_resp(s, &p);
+if(!p)
+ {
+ /* 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 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");
+ 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");
+ else
+ DEBUG(D_tls) debug_printf(" error parsing response\n");
+ OCSP_RESPONSE_free(rsp);
+ return 0;
+ }
+
+/* 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;
+ int status, reason;
+ ASN1_GENERALIZEDTIME *rev, *thisupd, *nextupd;
+
+ DEBUG(D_tls) bp = BIO_new_fp(stderr, BIO_NOCLOSE);
+
+ /*OCSP_RESPONSE_print(bp, rsp, 0); extreme debug: stapling content */
+
+ /* 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)
+ {
+ tls_out.ocsp = OCSP_FAILED;
+ BIO_printf(bp, "OCSP response verify failure\n");
+ ERR_print_errors(bp);
+ i = cbinfo->u_ocsp.client.verify_required ? 0 : 1;
+ goto out;
+ }
+
+ BIO_printf(bp, "OCSP response well-formed and signed OK\n");
+
+ {
+ STACK_OF(OCSP_SINGLERESP) * sresp = bs->tbsResponseData->responses;
+ OCSP_SINGLERESP * single;
+
+ if (sk_OCSP_SINGLERESP_num(sresp) != 1)
+ {
+ 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);
+ }
+
+ 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))
+ {
+ tls_out.ocsp = OCSP_FAILED;
+ DEBUG(D_tls) ERR_print_errors(bp);
+ log_write(0, LOG_MAIN, "Server OSCP dates invalid");
+ i = cbinfo->u_ocsp.client.verify_required ? 0 : 1;
+ }
+ else
+ {
+ 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*/
+
/*************************************************
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)
Returns: OK/DEFER/FAIL
*/
static int
-tls_init(host_item *host, uschar *dhparam, uschar *certificate,
- uschar *privatekey, address_item *addr)
+tls_init(SSL_CTX **ctxp, host_item *host, uschar *dhparam, uschar *certificate,
+ uschar *privatekey,
+#ifdef EXPERIMENTAL_OCSP
+ uschar *ocsp_file,
+#endif
+ address_item *addr, tls_ext_ctx_cb ** cbp)
{
long init_options;
int rc;
cbinfo = store_malloc(sizeof(tls_ext_ctx_cb));
cbinfo->certificate = certificate;
cbinfo->privatekey = privatekey;
+#ifdef EXPERIMENTAL_OCSP
+if ((cbinfo->is_server = host==NULL))
+ {
+ cbinfo->u_ocsp.server.file = ocsp_file;
+ cbinfo->u_ocsp.server.file_expanded = NULL;
+ cbinfo->u_ocsp.server.response = NULL;
+ }
+else
+ cbinfo->u_ocsp.client.verify_store = NULL;
+#endif
cbinfo->dhparam = dhparam;
cbinfo->host = host;
EVP_add_digest(EVP_sha256());
#endif
-/* Create a context */
+/* Create a context.
+The OpenSSL docs in 1.0.1b have not been updated to clarify TLS variant
+negotiation in the different methods; as far as I can tell, the only
+*_{server,client}_method which allows negotiation is SSLv23, which exists even
+when OpenSSL is built without SSLv2 support.
+By disabling with openssl_options, we can let admins re-enable with the
+existing knob. */
-ctx = SSL_CTX_new((host == NULL)?
+*ctxp = SSL_CTX_new((host == NULL)?
SSLv23_server_method() : SSLv23_client_method());
-if (ctx == NULL) return tls_error(US"SSL_CTX_new", host, NULL);
+if (*ctxp == NULL) return tls_error(US"SSL_CTX_new", host, NULL);
/* It turns out that we need to seed the random number generator this early in
order to get the full complement of ciphers to work. It took me roughly a day
/* Set up the information callback, which outputs if debugging is at a suitable
level. */
-SSL_CTX_set_info_callback(ctx, (void (*)())info_callback);
+SSL_CTX_set_info_callback(*ctxp, (void (*)())info_callback);
/* Automatically re-try reads/writes after renegotiation. */
-(void) SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY);
+(void) SSL_CTX_set_mode(*ctxp, SSL_MODE_AUTO_RETRY);
/* Apply administrator-supplied work-arounds.
Historically we applied just one requested option,
if (init_options)
{
DEBUG(D_tls) debug_printf("setting SSL CTX options: %#lx\n", init_options);
- if (!(SSL_CTX_set_options(ctx, init_options)))
+ if (!(SSL_CTX_set_options(*ctxp, init_options)))
return tls_error(string_sprintf(
"SSL_CTX_set_option(%#lx)", init_options), host, NULL);
}
/* Initialize with DH parameters if supplied */
-if (!init_dh(dhparam, host)) return DEFER;
+if (!init_dh(*ctxp, dhparam, host)) return DEFER;
-/* Set up certificate and key */
+/* Set up certificate and key (and perhaps OCSP info) */
-rc = tls_expand_session_files(ctx, cbinfo);
+rc = tls_expand_session_files(*ctxp, cbinfo);
if (rc != OK) return rc;
/* If we need to handle SNI, do so */
-#if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT)
-/* We always do this, so that $tls_sni is available even if not used in
-tls_certificate */
-SSL_CTX_set_tlsext_servername_callback(ctx, tls_servername_cb);
-SSL_CTX_set_tlsext_servername_arg(ctx, cbinfo);
+#ifdef EXIM_HAVE_OPENSSL_TLSEXT
+if (host == NULL) /* server */
+ {
+# ifdef EXPERIMENTAL_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
+ callback is invoked. */
+ if (cbinfo->u_ocsp.server.file)
+ {
+ SSL_CTX_set_tlsext_status_cb(server_ctx, tls_server_stapling_cb);
+ SSL_CTX_set_tlsext_status_arg(server_ctx, cbinfo);
+ }
+# endif
+ /* We always do this, so that $tls_sni is available even if not used in
+ tls_certificate */
+ SSL_CTX_set_tlsext_servername_callback(*ctxp, tls_servername_cb);
+ SSL_CTX_set_tlsext_servername_arg(*ctxp, cbinfo);
+ }
+# ifdef EXPERIMENTAL_OCSP
+else /* client */
+ if(ocsp_file) /* wanting stapling */
+ {
+ if (!(cbinfo->u_ocsp.client.verify_store = X509_STORE_new()))
+ {
+ DEBUG(D_tls) debug_printf("failed to create store for stapling verify\n");
+ return FAIL;
+ }
+ SSL_CTX_set_tlsext_status_cb(*ctxp, tls_client_stapling_cb);
+ SSL_CTX_set_tlsext_status_arg(*ctxp, cbinfo);
+ }
+# endif
#endif
/* Set up the RSA callback */
-SSL_CTX_set_tmp_rsa_callback(ctx, rsa_callback);
+SSL_CTX_set_tmp_rsa_callback(*ctxp, rsa_callback);
/* Finally, set the timeout, and we are done */
-SSL_CTX_set_timeout(ctx, ssl_session_timeout);
+SSL_CTX_set_timeout(*ctxp, ssl_session_timeout);
DEBUG(D_tls) debug_printf("Initialized TLS\n");
-static_cbinfo = cbinfo;
+*cbp = cbinfo;
return OK;
}
* Get name of cipher in use *
*************************************************/
-/* The answer is left in a static buffer, and tls_cipher is set to point
-to it.
-
+/*
Argument: pointer to an SSL structure for the connection
+ buffer to use for answer
+ size of buffer
+ pointer to number of bits for cipher
Returns: nothing
*/
static void
-construct_cipher_name(SSL *ssl)
+construct_cipher_name(SSL *ssl, uschar *cipherbuf, int bsize, int *bits)
{
-static uschar cipherbuf[256];
/* With OpenSSL 1.0.0a, this needs to be const but the documentation doesn't
yet reflect that. It should be a safe change anyway, even 0.9.8 versions have
the accessor functions use const in the prototype. */
const SSL_CIPHER *c;
-uschar *ver;
-
-switch (ssl->session->ssl_version)
- {
- case SSL2_VERSION:
- ver = US"SSLv2";
- break;
-
- case SSL3_VERSION:
- ver = US"SSLv3";
- break;
-
- case TLS1_VERSION:
- ver = US"TLSv1";
- break;
-
-#ifdef TLS1_1_VERSION
- case TLS1_1_VERSION:
- ver = US"TLSv1.1";
- break;
-#endif
+const uschar *ver;
-#ifdef TLS1_2_VERSION
- case TLS1_2_VERSION:
- ver = US"TLSv1.2";
- break;
-#endif
-
- default:
- ver = US"UNKNOWN";
- }
+ver = (const uschar *)SSL_get_version(ssl);
c = (const SSL_CIPHER *) SSL_get_current_cipher(ssl);
-SSL_CIPHER_get_bits(c, &tls_bits);
+SSL_CIPHER_get_bits(c, bits);
-string_format(cipherbuf, sizeof(cipherbuf), "%s:%s:%u", ver,
- SSL_CIPHER_get_name(c), tls_bits);
-tls_cipher = cipherbuf;
+string_format(cipherbuf, bsize, "%s:%s:%u", ver,
+ SSL_CIPHER_get_name(c), *bits);
DEBUG(D_tls) debug_printf("Cipher: %s\n", cipherbuf);
}
host NULL in a server; the remote host in a client
optional TRUE if called from a server for a host in tls_try_verify_hosts;
otherwise passed as FALSE
+ cert_vfy_cb Callback function for certificate verification
Returns: OK/DEFER/FAIL
*/
static int
-setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional)
+setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional,
+ int (*cert_vfy_cb)(int, X509_STORE_CTX *) )
{
uschar *expcerts, *expcrl;
if (!expand_check(certs, US"tls_verify_certificates", &expcerts))
return DEFER;
-if (expcerts != NULL)
+if (expcerts != NULL && *expcerts != '\0')
{
struct stat statbuf;
if (!SSL_CTX_set_default_verify_paths(sctx))
SSL_CTX_set_verify(sctx,
SSL_VERIFY_PEER | (optional? 0 : SSL_VERIFY_FAIL_IF_NO_PEER_CERT),
- verify_callback);
+ cert_vfy_cb);
}
return OK;
Arguments:
require_ciphers allowed ciphers
- ------------------------------------------------------
- require_mac list of allowed MACs ) Not used
- require_kx list of allowed key_exchange methods ) for
- require_proto list of allowed protocols ) OpenSSL
- ------------------------------------------------------
Returns: OK on success
DEFER for errors before the start of the negotiation
*/
int
-tls_server_start(uschar *require_ciphers, uschar *require_mac,
- uschar *require_kx, uschar *require_proto)
+tls_server_start(const uschar *require_ciphers)
{
int rc;
uschar *expciphers;
tls_ext_ctx_cb *cbinfo;
+static uschar cipherbuf[256];
/* Check for previous activation */
-if (tls_active >= 0)
+if (tls_in.active >= 0)
{
tls_error(US"STARTTLS received after TLS started", NULL, US"");
smtp_printf("554 Already in TLS\r\n");
/* Initialize the SSL library. If it fails, it will already have logged
the error. */
-rc = tls_init(NULL, tls_dhparam, tls_certificate, tls_privatekey, NULL);
+rc = tls_init(&server_ctx, NULL, tls_dhparam, tls_certificate, tls_privatekey,
+#ifdef EXPERIMENTAL_OCSP
+ tls_ocsp_file,
+#endif
+ NULL, &server_static_cbinfo);
if (rc != OK) return rc;
-cbinfo = static_cbinfo;
+cbinfo = server_static_cbinfo;
if (!expand_check(require_ciphers, US"tls_require_ciphers", &expciphers))
return FAIL;
/* In OpenSSL, cipher components are separated by hyphens. In GnuTLS, they
-are separated by underscores. So that I can use either form in my tests, and
-also for general convenience, we turn underscores into hyphens here. */
+were historically separated by underscores. So that I can use either form in my
+tests, and also for general convenience, we turn underscores into hyphens here.
+*/
if (expciphers != NULL)
{
uschar *s = expciphers;
while (*s != 0) { if (*s == '_') *s = '-'; s++; }
DEBUG(D_tls) debug_printf("required ciphers: %s\n", expciphers);
- if (!SSL_CTX_set_cipher_list(ctx, CS expciphers))
+ if (!SSL_CTX_set_cipher_list(server_ctx, CS expciphers))
return tls_error(US"SSL_CTX_set_cipher_list", NULL, NULL);
cbinfo->server_cipher_list = expciphers;
}
/* If this is a host for which certificate verification is mandatory or
optional, set up appropriately. */
-tls_certificate_verified = FALSE;
-verify_callback_called = FALSE;
+tls_in.certificate_verified = FALSE;
+server_verify_callback_called = FALSE;
if (verify_check_host(&tls_verify_hosts) == OK)
{
- rc = setup_certs(ctx, tls_verify_certificates, tls_crl, NULL, FALSE);
+ rc = setup_certs(server_ctx, tls_verify_certificates, tls_crl, NULL,
+ FALSE, verify_callback_server);
if (rc != OK) return rc;
- verify_optional = FALSE;
+ server_verify_optional = FALSE;
}
else if (verify_check_host(&tls_try_verify_hosts) == OK)
{
- rc = setup_certs(ctx, tls_verify_certificates, tls_crl, NULL, TRUE);
+ rc = setup_certs(server_ctx, tls_verify_certificates, tls_crl, NULL,
+ TRUE, verify_callback_server);
if (rc != OK) return rc;
- verify_optional = TRUE;
+ server_verify_optional = TRUE;
}
/* Prepare for new connection */
-if ((ssl = SSL_new(ctx)) == NULL) return tls_error(US"SSL_new", NULL, NULL);
+if ((server_ssl = SSL_new(server_ctx)) == NULL) return tls_error(US"SSL_new", NULL, NULL);
/* Warning: we used to SSL_clear(ssl) here, it was removed.
*
the response. Other smtp_printf() calls do not need it, because in non-TLS
mode, the fflush() happens when smtp_getc() is called. */
-SSL_set_session_id_context(ssl, sid_ctx, Ustrlen(sid_ctx));
-if (!tls_on_connect)
+SSL_set_session_id_context(server_ssl, sid_ctx, Ustrlen(sid_ctx));
+if (!tls_in.on_connect)
{
smtp_printf("220 TLS go ahead\r\n");
fflush(smtp_out);
/* Now negotiate the TLS session. We put our own timer on it, since it seems
that the OpenSSL library doesn't. */
-SSL_set_wfd(ssl, fileno(smtp_out));
-SSL_set_rfd(ssl, fileno(smtp_in));
-SSL_set_accept_state(ssl);
+SSL_set_wfd(server_ssl, fileno(smtp_out));
+SSL_set_rfd(server_ssl, fileno(smtp_in));
+SSL_set_accept_state(server_ssl);
DEBUG(D_tls) debug_printf("Calling SSL_accept\n");
sigalrm_seen = FALSE;
if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
-rc = SSL_accept(ssl);
+rc = SSL_accept(server_ssl);
alarm(0);
if (rc <= 0)
/* TLS has been set up. Adjust the input functions to read via TLS,
and initialize things. */
-construct_cipher_name(ssl);
+construct_cipher_name(server_ssl, cipherbuf, sizeof(cipherbuf), &tls_in.bits);
+tls_in.cipher = cipherbuf;
DEBUG(D_tls)
{
uschar buf[2048];
- if (SSL_get_shared_ciphers(ssl, CS buf, sizeof(buf)) != NULL)
+ if (SSL_get_shared_ciphers(server_ssl, CS buf, sizeof(buf)) != NULL)
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
+ smtp_read_response()/ip_recv().
+ Hence no need to duplicate for _in and _out.
+ */
ssl_xfer_buffer = store_malloc(ssl_xfer_buffer_size);
ssl_xfer_buffer_lwm = ssl_xfer_buffer_hwm = 0;
ssl_xfer_eof = ssl_xfer_error = 0;
receive_ferror = tls_ferror;
receive_smtp_buffered = tls_smtp_buffered;
-tls_active = fileno(smtp_out);
+tls_in.active = fileno(smtp_out);
return OK;
}
fd the fd of the connection
host connected host (for messages)
addr the first address
- dhparam DH parameter file
- certificate certificate file
- privatekey private key file
- verify_certs file for certificate verify
- crl file containing CRL
- require_ciphers list of allowed ciphers
- ------------------------------------------------------
- require_mac list of allowed MACs ) Not used
- require_kx list of allowed key_exchange methods ) for
- require_proto list of allowed protocols ) OpenSSL
- ------------------------------------------------------
- timeout startup timeout
+ ob smtp transport options
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 *dhparam,
- uschar *certificate, uschar *privatekey, uschar *verify_certs, uschar *crl,
- uschar *require_ciphers, uschar *require_mac, uschar *require_kx,
- uschar *require_proto, int timeout)
+tls_client_start(int fd, host_item *host, address_item *addr,
+ void *v_ob)
{
+smtp_transport_options_block * ob = v_ob;
static uschar txt[256];
uschar *expciphers;
X509* server_cert;
int rc;
+static uschar cipherbuf[256];
+#ifdef EXPERIMENTAL_OCSP
+BOOL require_ocsp = verify_check_this_host(&ob->hosts_require_ocsp,
+ NULL, host->name, host->address, NULL) == OK;
+BOOL request_ocsp = require_ocsp ? TRUE
+ : verify_check_this_host(&ob->hosts_request_ocsp,
+ NULL, host->name, host->address, NULL) == OK;
+#endif
-rc = tls_init(host, dhparam, certificate, privatekey, addr);
+rc = tls_init(&client_ctx, host, NULL,
+ ob->tls_certificate, ob->tls_privatekey,
+#ifdef EXPERIMENTAL_OCSP
+ (void *)(long)request_ocsp,
+#endif
+ addr, &client_static_cbinfo);
if (rc != OK) return rc;
-tls_certificate_verified = FALSE;
-verify_callback_called = FALSE;
+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
uschar *s = expciphers;
while (*s != 0) { if (*s == '_') *s = '-'; s++; }
DEBUG(D_tls) debug_printf("required ciphers: %s\n", expciphers);
- if (!SSL_CTX_set_cipher_list(ctx, CS expciphers))
+ if (!SSL_CTX_set_cipher_list(client_ctx, CS expciphers))
return tls_error(US"SSL_CTX_set_cipher_list", host, NULL);
}
-rc = setup_certs(ctx, verify_certs, crl, host, FALSE);
-if (rc != OK) return rc;
+/* stick to the old behaviour for compatibility if tls_verify_certificates is
+ set but both tls_verify_hosts and tls_try_verify_hosts is not set. Check only
+ the specified host patterns if one of them is defined */
+if ((!ob->tls_verify_hosts && !ob->tls_try_verify_hosts) ||
+ (verify_check_host(&ob->tls_verify_hosts) == OK))
+ {
+ if ((rc = setup_certs(client_ctx, ob->tls_verify_certificates,
+ ob->tls_crl, host, FALSE, verify_callback_client)) != OK)
+ return rc;
+ client_verify_optional = FALSE;
+ }
+else if (verify_check_host(&ob->tls_try_verify_hosts) == OK)
+ {
+ if ((rc = setup_certs(client_ctx, ob->tls_verify_certificates,
+ ob->tls_crl, host, TRUE, verify_callback_client)) != OK)
+ return rc;
+ client_verify_optional = TRUE;
+ }
+
+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 (ob->tls_sni)
+ {
+ if (!expand_check(ob->tls_sni, US"tls_sni", &tls_out.sni))
+ return FAIL;
+ if (tls_out.sni == NULL)
+ {
+ DEBUG(D_tls) debug_printf("Setting TLS SNI forced to fail, not sending\n");
+ }
+ else if (!Ustrlen(tls_out.sni))
+ tls_out.sni = NULL;
+ else
+ {
+#ifdef EXIM_HAVE_OPENSSL_TLSEXT
+ DEBUG(D_tls) debug_printf("Setting TLS SNI \"%s\"\n", tls_out.sni);
+ SSL_set_tlsext_host_name(client_ssl, tls_out.sni);
+#else
+ DEBUG(D_tls)
+ debug_printf("OpenSSL at build-time lacked SNI support, ignoring \"%s\"\n",
+ tls_out.sni);
+#endif
+ }
+ }
-if ((ssl = SSL_new(ctx)) == NULL) return tls_error(US"SSL_new", host, NULL);
-SSL_set_session_id_context(ssl, sid_ctx, Ustrlen(sid_ctx));
-SSL_set_fd(ssl, fd);
-SSL_set_connect_state(ssl);
+#ifdef EXPERIMENTAL_OCSP
+/* Request certificate status at connection-time. If the server
+does OCSP stapling we will get the callback (set in tls_init()) */
+if (request_ocsp)
+ {
+ 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
/* 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);
-rc = SSL_connect(ssl);
+alarm(ob->command_timeout);
+rc = SSL_connect(client_ssl);
alarm(0);
if (rc <= 0)
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 (ssl);
+/*XXX server_cert is never freed... use X509_free() */
+server_cert = SSL_get_peer_certificate (client_ssl);
if (server_cert)
{
- tls_peerdn = US X509_NAME_oneline(X509_get_subject_name(server_cert),
+ tls_out.peerdn = US X509_NAME_oneline(X509_get_subject_name(server_cert),
CS txt, sizeof(txt));
- tls_peerdn = txt;
+ tls_out.peerdn = txt; /*XXX a static buffer... */
}
else
- tls_peerdn = NULL;
+ tls_out.peerdn = NULL;
-construct_cipher_name(ssl); /* Sets tls_cipher */
+construct_cipher_name(client_ssl, cipherbuf, sizeof(cipherbuf), &tls_out.bits);
+tls_out.cipher = cipherbuf;
-tls_active = fd;
+/* 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;
}
Arguments: none
Returns: the next character or EOF
+
+Only used by the server-side TLS.
*/
int
int error;
int inbytes;
- DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", ssl,
+ DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", server_ssl,
ssl_xfer_buffer, ssl_xfer_buffer_size);
if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
- inbytes = SSL_read(ssl, CS ssl_xfer_buffer, ssl_xfer_buffer_size);
- error = SSL_get_error(ssl, inbytes);
+ inbytes = SSL_read(server_ssl, CS ssl_xfer_buffer, ssl_xfer_buffer_size);
+ error = SSL_get_error(server_ssl, inbytes);
alarm(0);
/* SSL_ERROR_ZERO_RETURN appears to mean that the SSL session has been
receive_ferror = smtp_ferror;
receive_smtp_buffered = smtp_buffered;
- SSL_free(ssl);
- ssl = NULL;
- tls_active = -1;
- tls_cipher = NULL;
- tls_peerdn = NULL;
+ SSL_free(server_ssl);
+ server_ssl = NULL;
+ tls_in.active = -1;
+ tls_in.bits = 0;
+ tls_in.cipher = NULL;
+ tls_in.peerdn = NULL;
+ tls_in.sni = NULL;
return smtp_getc();
}
Returns: the number of bytes read
-1 after a failed read
+
+Only used by the client-side TLS.
*/
int
-tls_read(uschar *buff, size_t len)
+tls_read(BOOL is_server, uschar *buff, size_t len)
{
+SSL *ssl = is_server ? server_ssl : client_ssl;
int inbytes;
int error;
/*
Arguments:
+ is_server channel specifier
buff buffer of data
len number of bytes
Returns: the number of bytes after a successful write,
-1 after a failed write
+
+Used by both server-side and client-side TLS.
*/
int
-tls_write(const uschar *buff, size_t len)
+tls_write(BOOL is_server, const uschar *buff, size_t len)
{
int outbytes;
int error;
int left = len;
+SSL *ssl = is_server ? server_ssl : client_ssl;
DEBUG(D_tls) debug_printf("tls_do_write(%p, %d)\n", buff, left);
while (left > 0)
log_write(0, LOG_MAIN, "SSL channel closed on write");
return -1;
+ case SSL_ERROR_SYSCALL:
+ log_write(0, LOG_MAIN, "SSL_write: (from %s) syscall: %s",
+ sender_fullhost ? sender_fullhost : US"<unknown>",
+ strerror(errno));
+
default:
log_write(0, LOG_MAIN, "SSL_write error %d", error);
return -1;
Arguments: TRUE if SSL_shutdown is to be called
Returns: nothing
+
+Used by both server-side and client-side TLS.
*/
void
-tls_close(BOOL shutdown)
+tls_close(BOOL is_server, BOOL shutdown)
{
-if (tls_active < 0) return; /* TLS was not active */
+SSL **sslp = is_server ? &server_ssl : &client_ssl;
+int *fdp = is_server ? &tls_in.active : &tls_out.active;
+
+if (*fdp < 0) return; /* TLS was not active */
if (shutdown)
{
DEBUG(D_tls) debug_printf("tls_close(): shutting down SSL\n");
- SSL_shutdown(ssl);
+ SSL_shutdown(*sslp);
+ }
+
+SSL_free(*sslp);
+*sslp = NULL;
+
+*fdp = -1;
+}
+
+
+
+
+/*************************************************
+* Let tls_require_ciphers be checked at startup *
+*************************************************/
+
+/* The tls_require_ciphers option, if set, must be something which the
+library can parse.
+
+Returns: NULL on success, or error message
+*/
+
+uschar *
+tls_validate_require_cipher(void)
+{
+SSL_CTX *ctx;
+uschar *s, *expciphers, *err;
+
+/* this duplicates from tls_init(), we need a better "init just global
+state, for no specific purpose" singleton function of our own */
+
+SSL_load_error_strings();
+OpenSSL_add_ssl_algorithms();
+#if (OPENSSL_VERSION_NUMBER >= 0x0090800fL) && !defined(OPENSSL_NO_SHA256)
+/* SHA256 is becoming ever more popular. This makes sure it gets added to the
+list of available digests. */
+EVP_add_digest(EVP_sha256());
+#endif
+
+if (!(tls_require_ciphers && *tls_require_ciphers))
+ return NULL;
+
+if (!expand_check(tls_require_ciphers, US"tls_require_ciphers", &expciphers))
+ return US"failed to expand tls_require_ciphers";
+
+if (!(expciphers && *expciphers))
+ return NULL;
+
+/* normalisation ripped from above */
+s = expciphers;
+while (*s != 0) { if (*s == '_') *s = '-'; s++; }
+
+err = NULL;
+
+ctx = SSL_CTX_new(SSLv23_server_method());
+if (!ctx)
+ {
+ ERR_error_string(ERR_get_error(), ssl_errstring);
+ return string_sprintf("SSL_CTX_new() failed: %s", ssl_errstring);
+ }
+
+DEBUG(D_tls)
+ debug_printf("tls_require_ciphers expands to \"%s\"\n", expciphers);
+
+if (!SSL_CTX_set_cipher_list(ctx, CS expciphers))
+ {
+ ERR_error_string(ERR_get_error(), ssl_errstring);
+ err = string_sprintf("SSL_CTX_set_cipher_list(%s) failed", expciphers);
}
-SSL_free(ssl);
-ssl = NULL;
+SSL_CTX_free(ctx);
-tls_active = -1;
+return err;
}
it can result in serious failures, including crashing with a SIGSEGV. So
report the version found by the compiler and the run-time version.
+Note: some OS vendors backport security fixes without changing the version
+number/string, and the version date remains unchanged. The _build_ date
+will change, so we can more usefully assist with version diagnosis by also
+reporting the build date.
+
Arguments: a FILE* to print the results to
Returns: nothing
*/
tls_version_report(FILE *f)
{
fprintf(f, "Library version: OpenSSL: Compile: %s\n"
- " Runtime: %s\n",
+ " Runtime: %s\n"
+ " : %s\n",
OPENSSL_VERSION_TEXT,
- SSLeay_version(SSLEAY_VERSION));
+ SSLeay_version(SSLEAY_VERSION),
+ SSLeay_version(SSLEAY_BUILT_ON));
+/* third line is 38 characters for the %s and the line is 73 chars long;
+the OpenSSL output includes a "built on: " prefix already. */
}
/*************************************************
-* Pseudo-random number generation *
+* Random number generation *
*************************************************/
/* Pseudo-random number generation. The result is not expected to be
*/
int
-pseudo_random_number(int max)
+vaguely_random_number(int max)
{
unsigned int r;
int i, needed_len;
+static pid_t pidlast = 0;
+pid_t pidnow;
uschar *p;
uschar smallbuf[sizeof(r)];
if (max <= 1)
return 0;
+pidnow = getpid();
+if (pidnow != pidlast)
+ {
+ /* Although OpenSSL documents that "OpenSSL makes sure that the PRNG state
+ is unique for each thread", this doesn't apparently apply across processes,
+ so our own warning from vaguely_random_number_fallback() applies here too.
+ Fix per PostgreSQL. */
+ if (pidlast != 0)
+ RAND_cleanup();
+ pidlast = pidnow;
+ }
+
/* OpenSSL auto-seeds from /dev/random, etc, but this a double-check. */
if (!RAND_status())
{
needed_len = i;
/* We do not care if crypto-strong */
-(void) RAND_pseudo_bytes(smallbuf, needed_len);
+i = RAND_pseudo_bytes(smallbuf, needed_len);
+if (i < 0)
+ {
+ DEBUG(D_all)
+ debug_printf("OpenSSL RAND_pseudo_bytes() not supported by RAND method, using fallback.\n");
+ return vaguely_random_number_fallback(max);
+ }
+
r = 0;
for (p = smallbuf; needed_len; --needed_len, ++p)
{
to apply.
This list is current as of:
- ==> 1.0.1b <== */
+ ==> 1.0.1b <==
+Plus SSL_OP_SAFARI_ECDHE_ECDSA_BUG from 2013-June patch/discussion on openssl-dev
+*/
static struct exim_openssl_option exim_openssl_options[] = {
/* KEEP SORTED ALPHABETICALLY! */
#ifdef SSL_OP_ALL
#ifdef SSL_OP_NO_TLSv1_2
{ US"no_tlsv1_2", SSL_OP_NO_TLSv1_2 },
#endif
+#ifdef SSL_OP_SAFARI_ECDHE_ECDSA_BUG
+ { US"safari_ecdhe_ecdsa_bug", SSL_OP_SAFARI_ECDHE_ECDSA_BUG },
+#endif
#ifdef SSL_OP_SINGLE_DH_USE
{ US"single_dh_use", SSL_OP_SINGLE_DH_USE },
#endif
BOOL adding, item_parsed;
result = 0L;
-/* Prior to 4.78 we or'd in SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; removed
+/* Prior to 4.80 we or'd in SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; removed
* from default because it increases BEAST susceptibility. */
+#ifdef SSL_OP_NO_SSLv2
+result |= SSL_OP_NO_SSLv2;
+#endif
if (option_spec == NULL)
{
return TRUE;
}
+/* vi: aw ai sw=2
+*/
/* End of tls-openssl.c */