* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2017 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Portions Copyright (c) The OpenSSL Project 1999 */
#ifndef DISABLE_OCSP
# include <openssl/ocsp.h>
#endif
-#ifdef EXPERIMENTAL_DANE
-# include <danessl.h>
+#ifdef SUPPORT_DANE
+# include "danessl.h"
#endif
#endif
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
# define EXIM_HAVE_OCSP_RESP_COUNT
-# define EXIM_HAVE_OPENSSL_DH_BITS
#else
# define EXIM_HAVE_EPHEM_RSA_KEX
# define EXIM_HAVE_RAND_PSEUDO
#endif
#if (OPENSSL_VERSION_NUMBER >= 0x0090800fL) && !defined(OPENSSL_NO_SHA256)
-# define EXIM_HAVE_SHA256
+# define EXIM_HAVE_SHA256 /*MMMM*/
#endif
/*
#ifndef LIBRESSL_VERSION_NUMBER
# if OPENSSL_VERSION_NUMBER >= 0x010100000L
# define EXIM_HAVE_OPENSSL_CHECKHOST
+# define EXIM_HAVE_OPENSSL_DH_BITS
# endif
# if OPENSSL_VERSION_NUMBER >= 0x010000000L \
&& (OPENSSL_VERSION_NUMBER & 0x0000ff000L) >= 0x000002000L
|| LIBRESSL_VERSION_NUMBER >= 0x20010000L
# if !defined(OPENSSL_NO_ECDH)
# if OPENSSL_VERSION_NUMBER >= 0x0090800fL
-# define EXIM_HAVE_ECDH
+# define EXIM_HAVE_ECDH /*MMMM*/
# endif
# if OPENSSL_VERSION_NUMBER >= 0x10002000L
# define EXIM_HAVE_OPENSSL_EC_NIST2NID
# define DISABLE_OCSP
#endif
+#ifdef EXIM_HAVE_OPENSSL_CHECKHOST
+# include <openssl/x509v3.h>
+#endif
+
/* Structure for collecting random data for seeding. */
typedef struct randstuff {
typedef struct tls_ext_ctx_cb {
uschar *certificate;
uschar *privatekey;
-#ifndef DISABLE_OCSP
BOOL is_server;
+#ifndef DISABLE_OCSP
STACK_OF(X509) *verify_stack; /* chain for verifying the proof */
union {
struct {
-#ifdef EXIM_HAVE_EPHEM_RSA_KEX
/*************************************************
* Callback to generate RSA key *
*************************************************/
/*
Arguments:
- s SSL connection
+ s SSL connection (not used)
export not used
keylength keylength
}
return rsa_key;
}
-#endif
}
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
/* This gets called *by* the dane library verify callback, which interposes
itself.
#endif
if (preverify_ok == 1)
- tls_out.dane_verified =
- tls_out.certificate_verified = TRUE;
+ {
+ tls_out.dane_verified = tls_out.certificate_verified = TRUE;
+#ifndef DISABLE_OCSP
+ if (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,
+ cert))
+ ERR_clear_error();
+ sk_X509_push(client_static_cbinfo->verify_stack, cert);
+ }
+#endif
+ }
else
{
int err = X509_STORE_CTX_get_error(x509ctx);
return preverify_ok;
}
-#endif /*EXPERIMENTAL_DANE*/
+#endif /*SUPPORT_DANE*/
/*************************************************
}
supply_response:
- cbinfo->u_ocsp.server.response = resp;
+ cbinfo->u_ocsp.server.response = resp; /*XXX stack?*/
return;
bad:
{
extern char ** environ;
uschar ** p;
- if (environ) for (p = USS environ; *p != NULL; p++)
+ if (environ) for (p = USS environ; *p; p++)
if (Ustrncmp(*p, "EXIM_TESTHARNESS_DISABLE_OCSPVALIDITYCHECK", 42) == 0)
{
DEBUG(D_tls) debug_printf("Supplying known bad OCSP response\n");
goto err;
where = US"generating pkey";
- /* deprecated, use RSA_generate_key_ex() */
-if (!(rsa = RSA_generate_key(1024, RSA_F4, NULL, NULL)))
+if (!(rsa = rsa_callback(NULL, 0, 1024)))
goto err;
where = US"assigning pkey";
+static int
+tls_add_certfile(SSL_CTX * sctx, tls_ext_ctx_cb * cbinfo, uschar * file,
+ uschar ** errstr)
+{
+DEBUG(D_tls) debug_printf("tls_certificate file %s\n", file);
+if (!SSL_CTX_use_certificate_chain_file(sctx, CS file))
+ return tls_error(string_sprintf(
+ "SSL_CTX_use_certificate_chain_file file=%s", file),
+ cbinfo->host, NULL, errstr);
+return 0;
+}
+
+static int
+tls_add_pkeyfile(SSL_CTX * sctx, tls_ext_ctx_cb * cbinfo, uschar * file,
+ uschar ** errstr)
+{
+DEBUG(D_tls) debug_printf("tls_privatekey file %s\n", file);
+if (!SSL_CTX_use_PrivateKey_file(sctx, CS file, SSL_FILETYPE_PEM))
+ return tls_error(string_sprintf(
+ "SSL_CTX_use_PrivateKey_file file=%s", file), cbinfo->host, NULL, errstr);
+return 0;
+}
+
+
/*************************************************
* Expand key and cert file specs *
*************************************************/
if (!cbinfo->certificate)
{
- if (cbinfo->host) /* client */
+ if (!cbinfo->is_server) /* client */
return OK;
/* server */
if (tls_install_selfsign(sctx, errstr) != OK)
}
else
{
+ int err;
+
if (Ustrstr(cbinfo->certificate, US"tls_sni") ||
Ustrstr(cbinfo->certificate, US"tls_in_sni") ||
Ustrstr(cbinfo->certificate, US"tls_out_sni")
if (!expand_check(cbinfo->certificate, US"tls_certificate", &expanded, errstr))
return DEFER;
- if (expanded != NULL)
- {
- DEBUG(D_tls) debug_printf("tls_certificate file %s\n", expanded);
- if (!SSL_CTX_use_certificate_chain_file(sctx, CS expanded))
- return tls_error(string_sprintf(
- "SSL_CTX_use_certificate_chain_file file=%s", expanded),
- cbinfo->host, NULL, errstr);
- }
+ if (expanded)
+ if (cbinfo->is_server)
+ {
+ const uschar * file_list = expanded;
+ int sep = 0;
+ uschar * file;
+
+ while (file = string_nextinlist(&file_list, &sep, NULL, 0))
+ if ((err = tls_add_certfile(sctx, cbinfo, file, errstr)))
+ return err;
+ }
+ else /* would there ever be a need for multiple client certs? */
+ if ((err = tls_add_certfile(sctx, cbinfo, expanded, errstr)))
+ return err;
if (cbinfo->privatekey != NULL &&
!expand_check(cbinfo->privatekey, US"tls_privatekey", &expanded, errstr))
key is in the same file as the certificate. */
if (expanded && *expanded)
- {
- DEBUG(D_tls) debug_printf("tls_privatekey file %s\n", expanded);
- if (!SSL_CTX_use_PrivateKey_file(sctx, CS expanded, SSL_FILETYPE_PEM))
- return tls_error(string_sprintf(
- "SSL_CTX_use_PrivateKey_file file=%s", expanded), cbinfo->host, NULL, errstr);
- }
+ if (cbinfo->is_server)
+ {
+ const uschar * file_list = expanded;
+ int sep = 0;
+ uschar * file;
+
+ while (file = string_nextinlist(&file_list, &sep, NULL, 0))
+ if ((err = tls_add_pkeyfile(sctx, cbinfo, file, errstr)))
+ return err;
+ }
+ else /* would there ever be a need for multiple client certs? */
+ if ((err = tls_add_pkeyfile(sctx, cbinfo, expanded, errstr)))
+ return err;
}
#ifndef DISABLE_OCSP
if (cbinfo->is_server && cbinfo->u_ocsp.server.file)
{
+ /*XXX stack*/
if (!expand_check(cbinfo->u_ocsp.server.file, US"tls_ocsp_file", &expanded, errstr))
return DEFER;
tls_server_stapling_cb(SSL *s, void *arg)
{
const tls_ext_ctx_cb *cbinfo = (tls_ext_ctx_cb *) arg;
-uschar *response_der;
+uschar *response_der; /*XXX blob */
int response_der_len;
+/*XXX stack: use SSL_get_certificate() to see which cert; from that work
+out which ocsp blob to send. Unfortunately, SSL_get_certificate is known
+buggy in current OpenSSL; it returns the last cert loaded always rather than
+the one actually presented. So we can't support a stack of OCSP proofs at
+this time. */
+
DEBUG(D_tls)
debug_printf("Received TLS status request (OCSP stapling); %s response\n",
cbinfo->u_ocsp.server.response ? "have" : "lack");
return SSL_TLSEXT_ERR_NOACK;
response_der = NULL;
-response_der_len = i2d_OCSP_RESPONSE(cbinfo->u_ocsp.server.response,
+response_der_len = i2d_OCSP_RESPONSE(cbinfo->u_ocsp.server.response, /*XXX stack*/
&response_der);
if (response_der_len <= 0)
return SSL_TLSEXT_ERR_NOACK;
int status, reason;
ASN1_GENERALIZEDTIME *rev, *thisupd, *nextupd;
- DEBUG(D_tls) bp = BIO_new_fp(stderr, BIO_NOCLOSE);
+ DEBUG(D_tls) bp = BIO_new_fp(debug_file, BIO_NOCLOSE);
/*OCSP_RESPONSE_print(bp, rsp, 0); extreme debug: stapling content */
cbinfo->u_ocsp.client.verify_store, 0)) <= 0)
{
tls_out.ocsp = OCSP_FAILED;
- if (LOGGING(tls_cipher))
- log_write(0, LOG_MAIN, "Received TLS cert status response, itself unverifiable");
+ if (LOGGING(tls_cipher)) log_write(0, LOG_MAIN,
+ "Received TLS cert status response, itself unverifiable: %s",
+ ERR_reason_error_string(ERR_peek_error()));
BIO_printf(bp, "OCSP response verify failure\n");
ERR_print_errors(bp);
+ OCSP_RESPONSE_print(bp, rsp, 0);
goto failed;
}
tls_init(SSL_CTX **ctxp, host_item *host, uschar *dhparam, uschar *certificate,
uschar *privatekey,
#ifndef DISABLE_OCSP
- uschar *ocsp_file,
+ uschar *ocsp_file, /*XXX stack, in server*/
#endif
address_item *addr, tls_ext_ctx_cb ** cbp, uschar ** errstr)
{
cbinfo = store_malloc(sizeof(tls_ext_ctx_cb));
cbinfo->certificate = certificate;
cbinfo->privatekey = privatekey;
+cbinfo->is_server = host==NULL;
#ifndef DISABLE_OCSP
cbinfo->verify_stack = NULL;
-if ((cbinfo->is_server = host==NULL))
+if (!host)
{
cbinfo->u_ocsp.server.file = ocsp_file;
cbinfo->u_ocsp.server.file_expanded = NULL;
gettimeofday(&r.tv, NULL);
r.p = getpid();
- RAND_seed((uschar *)(&r), sizeof(r));
- RAND_seed((uschar *)big_buffer, big_buffer_size);
- if (addr != NULL) RAND_seed((uschar *)addr, sizeof(addr));
+ RAND_seed(US (&r), sizeof(r));
+ RAND_seed(US big_buffer, big_buffer_size);
+ if (addr != NULL) RAND_seed(US addr, sizeof(addr));
if (!RAND_status())
return tls_error(US"RAND_status", host,
)
{
log_write(0, LOG_MAIN|LOG_PANIC,
- "failed to load cert hain from %s", file);
+ "failed to load cert chain from %s", file);
return DEFER;
}
#endif
rc = tls_init(&server_ctx, NULL, tls_dhparam, tls_certificate, tls_privatekey,
#ifndef DISABLE_OCSP
- tls_ocsp_file,
+ tls_ocsp_file, /*XXX stack*/
#endif
NULL, &server_static_cbinfo, errstr);
if (rc != OK) return rc;
optional, set up appropriately. */
tls_in.certificate_verified = FALSE;
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
tls_in.dane_verified = FALSE;
#endif
server_verify_callback_called = FALSE;
smtp_read_response()/ip_recv().
Hence no need to duplicate for _in and _out.
*/
-ssl_xfer_buffer = store_malloc(ssl_xfer_buffer_size);
+if (!ssl_xfer_buffer) 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;
}
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
static int
dane_tlsa_load(SSL * ssl, host_item * host, dns_answer * dnsa, uschar ** errstr)
{
log_write(0, LOG_MAIN, "DANE error: No usable TLSA records");
return DEFER;
}
-#endif /*EXPERIMENTAL_DANE*/
+#endif /*SUPPORT_DANE*/
int
tls_client_start(int fd, host_item *host, address_item *addr,
transport_instance * tb,
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
dns_answer * tlsa_dnsa,
#endif
uschar ** errstr)
BOOL require_ocsp = FALSE;
#endif
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
tls_out.tlsa_usage = 0;
#endif
#ifndef DISABLE_OCSP
{
-# ifdef EXPERIMENTAL_DANE
+# ifdef SUPPORT_DANE
if ( tlsa_dnsa
&& ob->hosts_request_ocsp[0] == '*'
&& ob->hosts_request_ocsp[1] == '\0'
verify_check_given_host(&ob->hosts_require_ocsp, host) == OK))
request_ocsp = TRUE;
else
-# ifdef EXPERIMENTAL_DANE
+# ifdef SUPPORT_DANE
if (!request_ocsp)
# endif
request_ocsp =
return tls_error(US"SSL_CTX_set_cipher_list", host, NULL, errstr);
}
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
if (tlsa_dnsa)
{
SSL_CTX_set_verify(client_ctx,
}
}
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
if (tlsa_dnsa)
if ((rc = dane_tlsa_load(client_ssl, host, tlsa_dnsa, errstr)) != OK)
return rc;
#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
+# ifdef SUPPORT_DANE
if (request_ocsp)
{
const uschar * s;
rc = SSL_connect(client_ssl);
alarm(0);
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
if (tlsa_dnsa)
DANESSL_cleanup(client_ssl);
#endif
{
int outbytes, error, left;
SSL *ssl = is_server ? server_ssl : client_ssl;
-static uschar * corked = NULL;
-static int c_size = 0, c_len = 0;
+static gstring * corked = NULL;
-DEBUG(D_tls) debug_printf("%s(%p, %d%s)\n", __FUNCTION__,
- buff, left, more ? ", more" : "");
+DEBUG(D_tls) debug_printf("%s(%p, %lu%s)\n", __FUNCTION__,
+ buff, (unsigned long)len, more ? ", more" : "");
/* Lacking a CORK or MSG_MORE facility (such as GnuTLS has) we copy data when
"more" is notified. This hack is only ok if small amounts are involved AND only
if (is_server && (more || corked))
{
- corked = string_catn(corked, &c_size, &c_len, buff, len);
+ corked = string_catn(corked, buff, len);
if (more)
return len;
- buff = CUS corked;
- len = c_len;
- corked = NULL; c_size = c_len = 0;
+ buff = CUS corked->s;
+ len = corked->ptr;
+ corked = NULL;
}
for (left = len; left > 0;)
gettimeofday(&r.tv, NULL);
r.p = getpid();
- RAND_seed((uschar *)(&r), sizeof(r));
+ RAND_seed(US (&r), sizeof(r));
}
/* We're after pseudo-random, not random; if we still don't have enough data
in the internal PRNG then our options are limited. We could sleep and hope