* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2017 */
/* See the file NOTICE for conditions of use and distribution. */
/* Copyright (c) Phil Pennock 2012 */
#if GNUTLS_VERSION_NUMBER >= 0x030109
# define SUPPORT_CORK
#endif
+#if GNUTLS_VERSION_NUMBER >= 0x030506 && !defined(DISABLE_OCSP)
+# define SUPPORT_SRV_OCSP_STACK
+#endif
#ifndef DISABLE_OCSP
# include <gnutls/ocsp.h>
uschar *exp_tls_verify_certificates;
uschar *exp_tls_crl;
uschar *exp_tls_require_ciphers;
- uschar *exp_tls_ocsp_file;
const uschar *exp_tls_verify_cert_hostnames;
#ifndef DISABLE_EVENT
uschar *event_action;
.exp_tls_verify_certificates = NULL,
.exp_tls_crl = NULL,
.exp_tls_require_ciphers = NULL,
- .exp_tls_ocsp_file = NULL,
.exp_tls_verify_cert_hostnames = NULL,
#ifndef DISABLE_EVENT
.event_action = NULL,
/* Set this to control gnutls_global_set_log_level(); values 0 to 9 will setup
the library logging; a value less than 0 disables the calls to set up logging
-callbacks. */
+callbacks. Possibly GNuTLS also looks for an environment variable
+"GNUTLS_DEBUG_LEVEL". */
#ifndef EXIM_GNUTLS_LIBRARY_LOG_LEVEL
# define EXIM_GNUTLS_LIBRARY_LOG_LEVEL -1
#endif
# define EXIM_SERVER_DH_BITS_PRE2_12 1024
#endif
-#define exim_gnutls_err_check(Label) do { \
- if (rc != GNUTLS_E_SUCCESS) \
+#define exim_gnutls_err_check(rc, Label) do { \
+ if ((rc) != GNUTLS_E_SUCCESS) \
return tls_error((Label), gnutls_strerror(rc), host, errstr); \
} while (0)
DEBUG(D_tls) debug_printf("Initialising GnuTLS server params.\n");
rc = gnutls_dh_params_init(&dh_server_params);
-exim_gnutls_err_check(US"gnutls_dh_params_init");
+exim_gnutls_err_check(rc, US"gnutls_dh_params_init");
m.data = NULL;
m.size = 0;
if (m.data)
{
rc = gnutls_dh_params_import_pkcs3(dh_server_params, &m, GNUTLS_X509_FMT_PEM);
- exim_gnutls_err_check(US"gnutls_dh_params_import_pkcs3");
+ exim_gnutls_err_check(rc, US"gnutls_dh_params_import_pkcs3");
DEBUG(D_tls) debug_printf("Loaded fixed standard D-H parameters\n");
return OK;
}
rc = gnutls_dh_params_import_pkcs3(dh_server_params, &m, GNUTLS_X509_FMT_PEM);
free(m.data);
- exim_gnutls_err_check(US"gnutls_dh_params_import_pkcs3");
+ exim_gnutls_err_check(rc, US"gnutls_dh_params_import_pkcs3");
DEBUG(D_tls) debug_printf("read D-H parameters from file \"%s\"\n", filename);
}
debug_printf("requesting generation of %d bit Diffie-Hellman prime ...\n",
dh_bits_gen);
rc = gnutls_dh_params_generate2(dh_server_params, dh_bits_gen);
- exim_gnutls_err_check(US"gnutls_dh_params_generate2");
+ exim_gnutls_err_check(rc, US"gnutls_dh_params_generate2");
/* gnutls_dh_params_export_pkcs3() will tell us the exact size, every time,
and I confirmed that a NULL call to get the size first is how the GnuTLS
rc = gnutls_dh_params_export_pkcs3(dh_server_params, GNUTLS_X509_FMT_PEM,
m.data, &sz);
if (rc != GNUTLS_E_SHORT_MEMORY_BUFFER)
- exim_gnutls_err_check(US"gnutls_dh_params_export_pkcs3(NULL) sizing");
+ exim_gnutls_err_check(rc, US"gnutls_dh_params_export_pkcs3(NULL) sizing");
m.size = sz;
if (!(m.data = malloc(m.size)))
return tls_error(US"memory allocation failed", strerror(errno), NULL, errstr);
if (rc != GNUTLS_E_SUCCESS)
{
free(m.data);
- exim_gnutls_err_check(US"gnutls_dh_params_export_pkcs3() real");
+ exim_gnutls_err_check(rc, US"gnutls_dh_params_export_pkcs3() real");
}
m.size = sz; /* shrink by 1, probably */
+/* Add certificate and key, from files.
+
+Return:
+ Zero or negative: good. Negate value for certificate index if < 0.
+ Greater than zero: FAIL or DEFER code.
+*/
+
+static int
+tls_add_certfile(exim_gnutls_state_st * state, const host_item * host,
+ uschar * certfile, uschar * keyfile, uschar ** errstr)
+{
+int rc = gnutls_certificate_set_x509_key_file(state->x509_cred,
+ CS certfile, CS keyfile, GNUTLS_X509_FMT_PEM);
+if (rc < 0)
+ return tls_error(
+ string_sprintf("cert/key setup: cert=%s key=%s", certfile, keyfile),
+ gnutls_strerror(rc), host, errstr);
+return -rc;
+}
+
+
/*************************************************
* Variables re-expanded post-SNI *
*************************************************/
*/
static int
-tls_expand_session_files(exim_gnutls_state_st *state, uschar ** errstr)
+tls_expand_session_files(exim_gnutls_state_st * state, uschar ** errstr)
{
struct stat statbuf;
int rc;
if (!host) /* server */
if (!state->received_sni)
{
- if (state->tls_certificate &&
- (Ustrstr(state->tls_certificate, US"tls_sni") ||
- Ustrstr(state->tls_certificate, US"tls_in_sni") ||
- Ustrstr(state->tls_certificate, US"tls_out_sni")
- ))
+ if ( state->tls_certificate
+ && ( Ustrstr(state->tls_certificate, US"tls_sni")
+ || Ustrstr(state->tls_certificate, US"tls_in_sni")
+ || Ustrstr(state->tls_certificate, US"tls_out_sni")
+ ) )
{
DEBUG(D_tls) debug_printf("We will re-expand TLS session files if we receive SNI.\n");
state->trigger_sni_changes = TRUE;
}
rc = gnutls_certificate_allocate_credentials(&state->x509_cred);
-exim_gnutls_err_check(US"gnutls_certificate_allocate_credentials");
+exim_gnutls_err_check(rc, US"gnutls_certificate_allocate_credentials");
+
+#ifdef SUPPORT_SRV_OCSP_STACK
+gnutls_certificate_set_flags(state->x509_cred, GNUTLS_CERTIFICATE_API_V2);
+#endif
/* remember: expand_check_tlsvar() is expand_check() but fiddling with
state members, assuming consistent naming; and expand_check() returns
DEBUG(D_tls) debug_printf("TLS SNI: have a changed cert/key pair.\n");
}
- rc = gnutls_certificate_set_x509_key_file(state->x509_cred,
- CS state->exp_tls_certificate, CS state->exp_tls_privatekey,
- GNUTLS_X509_FMT_PEM);
- exim_gnutls_err_check(
- string_sprintf("cert/key setup: cert=%s key=%s",
- state->exp_tls_certificate, state->exp_tls_privatekey));
- DEBUG(D_tls) debug_printf("TLS: cert/key registered\n");
- } /* tls_certificate */
+ if (!host) /* server */
+ {
+ const uschar * clist = state->exp_tls_certificate;
+ const uschar * klist = state->exp_tls_privatekey;
+ const uschar * olist;
+ int csep = 0, ksep = 0, osep = 0, cnt = 0;
+ uschar * cfile, * kfile, * ofile;
+#ifndef DISABLE_OCSP
+ if (!expand_check(tls_ocsp_file, US"tls_ocsp_file", &ofile, errstr))
+ return DEFER;
+ olist = ofile;
+#endif
-/* Set the OCSP stapling server info */
+ while (cfile = string_nextinlist(&clist, &csep, NULL, 0))
+
+ if (!(kfile = string_nextinlist(&klist, &ksep, NULL, 0)))
+ return tls_error(US"cert/key setup: out of keys", NULL, host, errstr);
+ else if (0 < (rc = tls_add_certfile(state, host, cfile, kfile, errstr)))
+ return rc;
+ else
+ {
+ int gnutls_cert_index = -rc;
+ DEBUG(D_tls) debug_printf("TLS: cert/key %s registered\n", cfile);
+
+ /* Set the OCSP stapling server info */
#ifndef DISABLE_OCSP
-if ( !host /* server */
- && tls_ocsp_file
- )
- {
- if (gnutls_buggy_ocsp)
- {
- DEBUG(D_tls) debug_printf("GnuTLS library is buggy for OCSP; avoiding\n");
+ if (tls_ocsp_file)
+ if (gnutls_buggy_ocsp)
+ {
+ DEBUG(D_tls)
+ debug_printf("GnuTLS library is buggy for OCSP; avoiding\n");
+ }
+ else if ((ofile = string_nextinlist(&olist, &osep, NULL, 0)))
+ {
+ /* Use the full callback method for stapling just to get
+ observability. More efficient would be to read the file once only,
+ if it never changed (due to SNI). Would need restart on file update,
+ or watch datestamp. */
+
+# ifdef SUPPORT_SRV_OCSP_STACK
+ rc = gnutls_certificate_set_ocsp_status_request_function2(
+ state->x509_cred, gnutls_cert_index,
+ server_ocsp_stapling_cb, ofile);
+
+ exim_gnutls_err_check(rc,
+ US"gnutls_certificate_set_ocsp_status_request_function2");
+# else
+ if (cnt++ > 0)
+ {
+ DEBUG(D_tls)
+ debug_printf("oops; multiple OCSP files not supported\n");
+ break;
+ }
+ gnutls_certificate_set_ocsp_status_request_function(
+ state->x509_cred, server_ocsp_stapling_cb, ofile);
+# endif
+
+ DEBUG(D_tls) debug_printf("OCSP response file = %s\n", ofile);
+ }
+ else
+ DEBUG(D_tls) debug_printf("ran out of OCSP response files in list\n");
+#endif
+ }
}
else
{
- if (!expand_check(tls_ocsp_file, US"tls_ocsp_file",
- &state->exp_tls_ocsp_file, errstr))
- return DEFER;
-
- /* Use the full callback method for stapling just to get observability.
- More efficient would be to read the file once only, if it never changed
- (due to SNI). Would need restart on file update, or watch datestamp. */
-
- gnutls_certificate_set_ocsp_status_request_function(state->x509_cred,
- server_ocsp_stapling_cb, state->exp_tls_ocsp_file);
-
- DEBUG(D_tls) debug_printf("OCSP response file = %s\n", state->exp_tls_ocsp_file);
+ if (0 < (rc = tls_add_certfile(state, host,
+ state->exp_tls_certificate, state->exp_tls_privatekey, errstr)))
+ return rc;
+ DEBUG(D_tls) debug_printf("TLS: cert/key registered\n");
}
- }
-#endif
+
+ } /* tls_certificate */
/* Set the trusted CAs file if one is provided, and then add the CRL if one is
if (cert_count < 0)
{
rc = cert_count;
- exim_gnutls_err_check(US"setting certificate trust");
+ exim_gnutls_err_check(rc, US"setting certificate trust");
}
DEBUG(D_tls) debug_printf("Added %d certificate authorities.\n", cert_count);
if (cert_count < 0)
{
rc = cert_count;
- exim_gnutls_err_check(US"gnutls_certificate_set_x509_crl_file");
+ exim_gnutls_err_check(rc, US"gnutls_certificate_set_x509_crl_file");
}
DEBUG(D_tls) debug_printf("Processed %d CRLs.\n", cert_count);
}
/* Link the credentials to the session. */
rc = gnutls_credentials_set(state->session, GNUTLS_CRD_CERTIFICATE, state->x509_cred);
-exim_gnutls_err_check(US"gnutls_credentials_set");
+exim_gnutls_err_check(rc, US"gnutls_credentials_set");
return OK;
}
if (!gnutls_allow_auto_pkcs11)
{
rc = gnutls_pkcs11_init(GNUTLS_PKCS11_FLAG_MANUAL, NULL);
- exim_gnutls_err_check(US"gnutls_pkcs11_init");
+ exim_gnutls_err_check(rc, US"gnutls_pkcs11_init");
}
#endif
rc = gnutls_global_init();
- exim_gnutls_err_check(US"gnutls_global_init");
+ exim_gnutls_err_check(rc, US"gnutls_global_init");
#if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0
DEBUG(D_tls)
DEBUG(D_tls) debug_printf("initialising GnuTLS server session\n");
rc = gnutls_init(&state->session, GNUTLS_SERVER);
}
-exim_gnutls_err_check(US"gnutls_init");
+exim_gnutls_err_check(rc, US"gnutls_init");
state->host = host;
sz = Ustrlen(state->tlsp->sni);
rc = gnutls_server_name_set(state->session,
GNUTLS_NAME_DNS, state->tlsp->sni, sz);
- exim_gnutls_err_check(US"gnutls_server_name_set");
+ exim_gnutls_err_check(rc, US"gnutls_server_name_set");
}
}
else if (state->tls_sni)
DEBUG(D_tls) debug_printf("*** PROBABLY A BUG *** " \
- "have an SNI set for a client [%s]\n", state->tls_sni);
+ "have an SNI set for a server [%s]\n", state->tls_sni);
/* This is the priority string support,
http://www.gnutls.org/manual/html_node/Priority-Strings.html
p = US exim_default_gnutls_priority;
}
-exim_gnutls_err_check(string_sprintf(
+exim_gnutls_err_check(rc, string_sprintf(
"gnutls_priority_init(%s) failed at offset %ld, \"%.6s..\"",
p, errpos - CS p, errpos));
rc = gnutls_priority_set(state->session, state->priority_cache);
-exim_gnutls_err_check(US"gnutls_priority_set");
+exim_gnutls_err_check(rc, US"gnutls_priority_set");
gnutls_db_set_cache_expiration(state->session, ssl_session_timeout);
gnutls_datum_t * ocsp_response)
{
int ret;
+DEBUG(D_tls) debug_printf("OCSP stapling callback: %s\n", US ptr);
if ((ret = gnutls_load_file(ptr, ocsp_response)) < 0)
{