* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
/* See the file NOTICE for conditions of use and distribution. */
/* Portions Copyright (c) The OpenSSL Project 1999 */
&& (OPENSSL_VERSION_NUMBER & 0x0000ff000L) >= 0x000002000L
# define EXIM_HAVE_OPENSSL_CHECKHOST
# endif
+#endif
+#if !defined(LIBRESSL_VERSION_NUMBER) \
+ || LIBRESSL_VERSION_NUMBER >= 0x20010000L
# if !defined(OPENSSL_NO_ECDH)
# if OPENSSL_VERSION_NUMBER >= 0x0090800fL
# define EXIM_HAVE_ECDH
} server;
struct {
X509_STORE *verify_store; /* non-null if status requested */
+ STACK_OF(X509) *verify_stack;
BOOL verify_required;
} client;
} u_ocsp;
*/
static int
-tls_error(uschar * prefix, const host_item * host, uschar * msg)
+tls_error(uschar * prefix, const host_item * host, uschar * msg)
{
if (!msg)
{
#ifdef EXIM_HAVE_RSA_GENKEY_EX
if ( !BN_set_word(bn, (unsigned long)RSA_F4)
+ || !(rsa_key = RSA_new())
|| !RSA_generate_key_ex(rsa_key, keylength, bn, NULL)
)
#else
-rsa_key = RSA_generate_key(keylength, RSA_F4, NULL, NULL);
-if (rsa_key == NULL)
+if (!(rsa_key = RSA_generate_key(keylength, RSA_F4, NULL, NULL)))
#endif
{
if (!X509_STORE_add_cert(client_static_cbinfo->u_ocsp.client.verify_store,
cert))
ERR_clear_error();
+ sk_X509_push(client_static_cbinfo->u_ocsp.client.verify_stack, cert);
}
#endif
#ifndef DISABLE_EVENT
* Load OCSP information into state *
*************************************************/
+static STACK_OF(X509) *
+cert_stack_from_store(X509_STORE * store)
+{
+STACK_OF(X509_OBJECT) * roots= store->objs;
+STACK_OF(X509) * sk = sk_X509_new_null();
+int i;
+
+for(i = sk_X509_OBJECT_num(roots) - 1; i >= 0; i--)
+ {
+ X509_OBJECT * tmp_obj= sk_X509_OBJECT_value(roots, i);
+ if(tmp_obj->type == X509_LU_X509)
+ {
+ X509 * x = tmp_obj->data.x509;
+ sk_X509_push(sk, x);
+ }
+ }
+return sk;
+}
+
+static void
+cert_stack_free(STACK_OF(X509) * sk)
+{
+while (sk_X509_num(sk) > 0) (void) sk_X509_pop(sk);
+sk_X509_free(sk);
+}
+
+
+
/* 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.
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;
+BIO * bio;
+OCSP_RESPONSE * resp;
+OCSP_BASICRESP * basic_response;
+OCSP_SINGLERESP * single_response;
+ASN1_GENERALIZEDTIME * rev, * thisupd, * nextupd;
+X509_STORE * store;
+STACK_OF(X509) * sk;
unsigned long verify_flags;
int status, reason, i;
cbinfo->u_ocsp.server.response = NULL;
}
-bio = BIO_new_file(CS cbinfo->u_ocsp.server.file_expanded, "rb");
-if (!bio)
+if (!(bio = BIO_new_file(CS cbinfo->u_ocsp.server.file_expanded, "rb")))
{
DEBUG(D_tls) debug_printf("Failed to open OCSP response file \"%s\"\n",
cbinfo->u_ocsp.server.file_expanded);
return;
}
-status = OCSP_response_status(resp);
-if (status != OCSP_RESPONSE_STATUS_SUCCESSFUL)
+if ((status = OCSP_response_status(resp)) != 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)
+if (!(basic_response = OCSP_response_get1_basic(resp)))
{
DEBUG(D_tls)
debug_printf("OCSP response parse error: unable to extract basic response.\n");
}
store = SSL_CTX_get_cert_store(sctx);
+sk = cert_stack_from_store(store);
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)
+/* This does a full verify on the OCSP proof before we load it for serviing
+up; possibly overkill - just date-checks might be nice enough.
+
+OCSP_basic_verify takes a "store" arg, but does not
+use it for the chain verification, which is all we do
+when OCSP_NOVERIFY is set. The content from the wire
+"basic_response" and a cert-stack "sk" are all that is used.
+
+Seperately we might try to replace using OCSP_basic_verify() - which seems to not
+be a public interface into the OpenSSL library (there's no manual entry) -
+But what with? We also use OCSP_basic_verify in the client stapling callback.
+And there we NEED it; we miust verify that status... unless the
+library does it for us anyway? */
+
+if ((i = OCSP_basic_verify(basic_response, sk, NULL, verify_flags)) < 0)
{
- DEBUG(D_tls) {
+ DEBUG(D_tls)
+ {
ERR_error_string(ERR_get_error(), ssl_errstring);
debug_printf("OCSP response verify failure: %s\n", US ssl_errstring);
}
+ cert_stack_free(sk);
goto bad;
}
+cert_stack_free(sk);
/* 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
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)
+
+if (!(single_response = OCSP_resp_get0(basic_response, 0)))
{
DEBUG(D_tls)
debug_printf("Unable to get first response from OCSP basic response.\n");
+/* Create and install a selfsigned certificate, for use in server mode */
+
+static int
+tls_install_selfsign(SSL_CTX * sctx)
+{
+X509 * x509 = NULL;
+EVP_PKEY * pkey;
+RSA * rsa;
+X509_NAME * name;
+uschar * where;
+
+where = US"allocating pkey";
+if (!(pkey = EVP_PKEY_new()))
+ goto err;
+
+where = US"allocating cert";
+if (!(x509 = X509_new()))
+ goto err;
+
+where = US"generating pkey";
+ /* deprecated, use RSA_generate_key_ex() */
+if (!(rsa = RSA_generate_key(1024, RSA_F4, NULL, NULL)))
+ goto err;
+
+where = US"assiging pkey";
+if (!EVP_PKEY_assign_RSA(pkey, rsa))
+ goto err;
+
+X509_set_version(x509, 2); /* N+1 - version 3 */
+ASN1_INTEGER_set(X509_get_serialNumber(x509), 0);
+X509_gmtime_adj(X509_get_notBefore(x509), 0);
+X509_gmtime_adj(X509_get_notAfter(x509), (long)60 * 60); /* 1 hour */
+X509_set_pubkey(x509, pkey);
+
+name = X509_get_subject_name(x509);
+X509_NAME_add_entry_by_txt(name, "C",
+ MBSTRING_ASC, CUS "UK", -1, -1, 0);
+X509_NAME_add_entry_by_txt(name, "O",
+ MBSTRING_ASC, CUS "Exim Developers", -1, -1, 0);
+X509_NAME_add_entry_by_txt(name, "CN",
+ MBSTRING_ASC, CUS smtp_active_hostname, -1, -1, 0);
+X509_set_issuer_name(x509, name);
+
+where = US"signing cert";
+if (!X509_sign(x509, pkey, EVP_md5()))
+ goto err;
+
+where = US"installing selfsign cert";
+if (!SSL_CTX_use_certificate(sctx, x509))
+ goto err;
+
+where = US"installing selfsign key";
+if (!SSL_CTX_use_PrivateKey(sctx, pkey))
+ goto err;
+
+return OK;
+
+err:
+ (void) tls_error(where, NULL, NULL);
+ if (x509) X509_free(x509);
+ if (pkey) EVP_PKEY_free(pkey);
+ return DEFER;
+}
+
+
+
+
/*************************************************
* Expand key and cert file specs *
*************************************************/
{
uschar *expanded;
-if (cbinfo->certificate == NULL)
- return OK;
-
-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))
- return DEFER;
-
-if (expanded != NULL)
+if (!cbinfo->certificate)
{
- 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);
+ if (cbinfo->host) /* client */
+ return OK;
+ /* server */
+ if (tls_install_selfsign(sctx) != OK)
+ return DEFER;
}
+else
+ {
+ 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 (cbinfo->privatekey != NULL &&
- !expand_check(cbinfo->privatekey, US"tls_privatekey", &expanded))
- return DEFER;
+ if (!expand_check(cbinfo->certificate, US"tls_certificate", &expanded))
+ return DEFER;
-/* If expansion was forced to fail, key_expanded will be NULL. If the result
-of the expansion is an empty string, ignore it also, and assume the private
-key is in the same file as the certificate. */
+ 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);
+ }
-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);
+ if (cbinfo->privatekey != NULL &&
+ !expand_check(cbinfo->privatekey, US"tls_privatekey", &expanded))
+ return DEFER;
+
+ /* If expansion was forced to fail, key_expanded will be NULL. If the result
+ of the expansion is an empty string, ignore it also, and assume the private
+ 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);
+ }
}
#ifndef DISABLE_OCSP
/* 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,
+ if ((i = OCSP_basic_verify(bs, cbinfo->u_ocsp.client.verify_stack,
cbinfo->u_ocsp.client.verify_store, 0)) <= 0)
{
tls_out.ocsp = OCSP_FAILED;
cbinfo->u_ocsp.server.response = NULL;
}
else
+ {
cbinfo->u_ocsp.client.verify_store = NULL;
+ cbinfo->u_ocsp.client.verify_stack = NULL;
+ }
#endif
cbinfo->dhparam = dhparam;
cbinfo->server_cipher_list = NULL;
/* Set up certificate and key (and perhaps OCSP info) */
-rc = tls_expand_session_files(*ctxp, cbinfo);
-if (rc != OK) return rc;
+if ((rc = tls_expand_session_files(*ctxp, cbinfo)) != OK)
+ return rc;
/* If we need to handle SNI, do so */
#ifdef EXIM_HAVE_OPENSSL_TLSEXT
DEBUG(D_tls) debug_printf("failed to create store for stapling verify\n");
return FAIL;
}
+ if (!(cbinfo->u_ocsp.client.verify_stack = sk_X509_new_null()))
+ {
+ DEBUG(D_tls) debug_printf("failed to create stack 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);
}
ssl_xfer_eof = ssl_xfer_error = 0;
receive_getc = tls_getc;
+receive_get_cache = tls_get_cache;
receive_ungetc = tls_ungetc;
receive_feof = tls_feof;
receive_ferror = tls_ferror;
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 0: /* action not taken */
case 1: break;
}
DEBUG(D_tls) debug_printf("Got SSL_ERROR_ZERO_RETURN\n");
receive_getc = smtp_getc;
+ receive_get_cache = smtp_get_cache;
receive_ungetc = smtp_ungetc;
receive_feof = smtp_feof;
receive_ferror = smtp_ferror;
return ssl_xfer_buffer[ssl_xfer_buffer_lwm++];
}
+void
+tls_get_cache()
+{
+#ifndef DISABLE_DKIM
+int n = ssl_xfer_buffer_hwm - ssl_xfer_buffer_lwm;
+if (n > 0)
+ dkim_exim_verify_feed(ssl_xfer_buffer+ssl_xfer_buffer_lwm, n);
+#endif
+}
+
/*************************************************
switch (error)
{
case SSL_ERROR_SSL:
- ERR_error_string(ERR_get_error(), ssl_errstring);
- log_write(0, LOG_MAIN, "TLS error (SSL_write): %s", ssl_errstring);
- return -1;
+ ERR_error_string(ERR_get_error(), ssl_errstring);
+ log_write(0, LOG_MAIN, "TLS error (SSL_write): %s", ssl_errstring);
+ return -1;
case SSL_ERROR_NONE:
- left -= outbytes;
- buff += outbytes;
- break;
+ left -= outbytes;
+ buff += outbytes;
+ break;
case SSL_ERROR_ZERO_RETURN:
- log_write(0, LOG_MAIN, "SSL channel closed on write");
- return -1;
+ 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));
+ log_write(0, LOG_MAIN, "SSL_write: (from %s) syscall: %s",
+ sender_fullhost ? sender_fullhost : US"<unknown>",
+ strerror(errno));
+ return -1;
default:
- log_write(0, LOG_MAIN, "SSL_write error %d", error);
- return -1;
+ log_write(0, LOG_MAIN, "SSL_write error %d", error);
+ return -1;
}
}
return len;
keep_c = *end;
*end = '\0';
item_parsed = tls_openssl_one_option_parse(s, &item);
+ *end = keep_c;
if (!item_parsed)
{
DEBUG(D_tls) debug_printf("openssl option setting unrecognised: \"%s\"\n", s);
result |= item;
else
result &= ~item;
- *end = keep_c;
s = end;
}