* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
/* See the file NOTICE for conditions of use and distribution. */
/* Copyright (c) Phil Pennock 2012 */
/* needed to disable PKCS11 autoload unless requested */
#if GNUTLS_VERSION_NUMBER >= 0x020c00
# include <gnutls/pkcs11.h>
+# define SUPPORT_PARAM_TO_PK_BITS
#endif
-#ifdef EXPERIMENTAL_OCSP
+#if GNUTLS_VERSION_NUMBER < 0x030103 && !defined(DISABLE_OCSP)
+# warning "GnuTLS library version too old; define DISABLE_OCSP in Makefile"
+# define DISABLE_OCSP
+#endif
+#if GNUTLS_VERSION_NUMBER < 0x020a00 && !defined(DISABLE_EVENT)
+# warning "GnuTLS library version too old; tls:cert event unsupported"
+# define DISABLE_EVENT
+#endif
+#if GNUTLS_VERSION_NUMBER >= 0x030306
+# define SUPPORT_CA_DIR
+#else
+# undef SUPPORT_CA_DIR
+#endif
+#if GNUTLS_VERSION_NUMBER >= 0x030014
+# define SUPPORT_SYSDEFAULT_CABUNDLE
+#endif
+
+#ifndef DISABLE_OCSP
# include <gnutls/ocsp.h>
#endif
/* Values for verify_requirement */
-enum peer_verify_requirement { VERIFY_NONE, VERIFY_OPTIONAL, VERIFY_REQUIRED };
+enum peer_verify_requirement
+ { VERIFY_NONE, VERIFY_OPTIONAL, VERIFY_REQUIRED };
/* This holds most state for server or client; with this, we can set up an
outbound TLS-enabled connection in an ACL callout, while not stomping all
*/
typedef struct exim_gnutls_state {
- gnutls_session_t session;
+ gnutls_session_t session;
gnutls_certificate_credentials_t x509_cred;
- gnutls_priority_t priority_cache;
+ gnutls_priority_t priority_cache;
enum peer_verify_requirement verify_requirement;
- int fd_in;
- int fd_out;
- BOOL peer_cert_verified;
- BOOL trigger_sni_changes;
- BOOL have_set_peerdn;
+ int fd_in;
+ int fd_out;
+ BOOL peer_cert_verified;
+ BOOL trigger_sni_changes;
+ BOOL have_set_peerdn;
const struct host_item *host;
- uschar *peerdn;
- uschar *ciphersuite;
- uschar *received_sni;
+ gnutls_x509_crt_t peercert;
+ uschar *peerdn;
+ uschar *ciphersuite;
+ uschar *received_sni;
const uschar *tls_certificate;
const uschar *tls_privatekey;
const uschar *tls_verify_certificates;
const uschar *tls_crl;
const uschar *tls_require_ciphers;
+
uschar *exp_tls_certificate;
uschar *exp_tls_privatekey;
- uschar *exp_tls_sni;
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;
+#endif
tls_support *tlsp; /* set in tls_init() */
NULL, NULL, NULL, VERIFY_NONE, -1, -1, FALSE, FALSE, FALSE,
NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL,
+#ifndef DISABLE_EVENT
+ NULL,
+#endif
NULL,
NULL, 0, 0, 0, 0,
};
single-threaded to keep from processing data on an inbound TLS connection while
talking to another TLS connection for an outbound check. This does mean that
there's no way for heart-beats to be responded to, for the duration of the
-second connection. */
+second connection.
+XXX But see gnutls_session_get_ptr()
+*/
static exim_gnutls_state_st state_server, state_client;
static BOOL exim_gnutls_base_init_done = FALSE;
+#ifndef DISABLE_OCSP
+static BOOL gnutls_buggy_ocsp = FALSE;
+#endif
+
/* ------------------------------------------------------------------------ */
/* macros */
the library logging; a value less than 0 disables the calls to set up logging
callbacks. */
#ifndef EXIM_GNUTLS_LIBRARY_LOG_LEVEL
-#define EXIM_GNUTLS_LIBRARY_LOG_LEVEL -1
+# define EXIM_GNUTLS_LIBRARY_LOG_LEVEL -1
#endif
#ifndef EXIM_CLIENT_DH_MIN_BITS
-#define EXIM_CLIENT_DH_MIN_BITS 1024
+# define EXIM_CLIENT_DH_MIN_BITS 1024
#endif
/* With GnuTLS 2.12.x+ we have gnutls_sec_param_to_pk_bits() with which we
can ask for a bit-strength. Without that, we stick to the constant we had
before, for now. */
#ifndef EXIM_SERVER_DH_BITS_PRE2_12
-#define EXIM_SERVER_DH_BITS_PRE2_12 1024
+# define EXIM_SERVER_DH_BITS_PRE2_12 1024
#endif
#define exim_gnutls_err_check(Label) do { \
#define expand_check_tlsvar(Varname) expand_check(state->Varname, US #Varname, &state->exp_##Varname)
#if GNUTLS_VERSION_NUMBER >= 0x020c00
-#define HAVE_GNUTLS_SESSION_CHANNEL_BINDING
-#define HAVE_GNUTLS_SEC_PARAM_CONSTANTS
-#define HAVE_GNUTLS_RND
+# define HAVE_GNUTLS_SESSION_CHANNEL_BINDING
+# define HAVE_GNUTLS_SEC_PARAM_CONSTANTS
+# define HAVE_GNUTLS_RND
/* The security fix we provide with the gnutls_allow_auto_pkcs11 option
* (4.82 PP/09) introduces a compatibility regression. The symbol simply
* isn't available sometimes, so this needs to become a conditional
* compilation; the sanest way to deal with this being a problem on
* older OSes is to block it in the Local/Makefile with this compiler
* definition */
-#ifndef AVOID_GNUTLS_PKCS11
-#define HAVE_GNUTLS_PKCS11
-#endif /* AVOID_GNUTLS_PKCS11 */
+# ifndef AVOID_GNUTLS_PKCS11
+# define HAVE_GNUTLS_PKCS11
+# endif /* AVOID_GNUTLS_PKCS11 */
#endif
static int exim_sni_handling_cb(gnutls_session_t session);
+#ifndef DISABLE_OCSP
+static int server_ocsp_stapling_cb(gnutls_session_t session, void * ptr,
+ gnutls_datum_t * ocsp_response);
+#endif
{
if (host)
{
- log_write(0, LOG_MAIN, "TLS error on connection to %s [%s] (%s)%s%s",
+ log_write(0, LOG_MAIN, "H=%s [%s] TLS error on connection (%s)%s%s",
host->name, host->address, prefix, msg ? ": " : "", msg ? msg : "");
return FAIL;
}
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%s",
conn_info, prefix, msg ? ": " : "", msg ? msg : "");
return DEFER;
* Set various Exim expansion vars *
*************************************************/
+#define exim_gnutls_cert_err(Label) \
+ do \
+ { \
+ if (rc != GNUTLS_E_SUCCESS) \
+ { \
+ DEBUG(D_tls) debug_printf("TLS: cert problem: %s: %s\n", \
+ (Label), gnutls_strerror(rc)); \
+ return rc; \
+ } \
+ } while (0)
+
+static int
+import_cert(const gnutls_datum_t * cert, gnutls_x509_crt_t * crtp)
+{
+int rc;
+
+rc = gnutls_x509_crt_init(crtp);
+exim_gnutls_cert_err(US"gnutls_x509_crt_init (crt)");
+
+rc = gnutls_x509_crt_import(*crtp, cert, GNUTLS_X509_FMT_DER);
+exim_gnutls_cert_err(US"failed to import certificate [gnutls_x509_crt_import(cert)]");
+
+return rc;
+}
+
+#undef exim_gnutls_cert_err
+
+
/* We set various Exim global variables from the state, once a session has
been established. With TLS callouts, may need to change this to stack
variables, or just re-call it with the server state after client callout
has finished.
-Make sure anything set here is inset in tls_getc().
+Make sure anything set here is unset in tls_getc().
Sets:
tls_active fd
tls_certificate_verified bool indicator
tls_channelbinding_b64 for some SASL mechanisms
tls_cipher a string
+ tls_peercert pointer to library internal
tls_peerdn a string
tls_sni a (UTF-8) string
+ tls_ourcert pointer to library internal
Argument:
state the relevant exim_gnutls_state_st *
*/
static void
-extract_exim_vars_from_tls_state(exim_gnutls_state_st *state, BOOL is_server)
+extract_exim_vars_from_tls_state(exim_gnutls_state_st * state)
{
gnutls_cipher_algorithm_t cipher;
#ifdef HAVE_GNUTLS_SESSION_CHANNEL_BINDING
int rc;
gnutls_datum_t channel;
#endif
+tls_support * tlsp = state->tlsp;
-state->tlsp->active = state->fd_out;
+tlsp->active = state->fd_out;
cipher = gnutls_cipher_get(state->session);
/* returns size in "bytes" */
-state->tlsp->bits = gnutls_cipher_get_key_size(cipher) * 8;
+tlsp->bits = gnutls_cipher_get_key_size(cipher) * 8;
-state->tlsp->cipher = state->ciphersuite;
+tlsp->cipher = state->ciphersuite;
DEBUG(D_tls) debug_printf("cipher: %s\n", state->ciphersuite);
-state->tlsp->certificate_verified = state->peer_cert_verified;
+tlsp->certificate_verified = state->peer_cert_verified;
/* note that tls_channelbinding_b64 is not saved to the spool file, since it's
only available for use for authenticators while this TLS session is running. */
} else {
old_pool = store_pool;
store_pool = POOL_PERM;
- tls_channelbinding_b64 = auth_b64encode(channel.data, (int)channel.size);
+ tls_channelbinding_b64 = b64encode(channel.data, (int)channel.size);
store_pool = old_pool;
DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage.\n");
}
#endif
-state->tlsp->peerdn = state->peerdn;
-state->tlsp->sni = state->received_sni;
+/* peercert is set in peer_status() */
+tlsp->peerdn = state->peerdn;
+tlsp->sni = state->received_sni;
+
+/* record our certificate */
+ {
+ const gnutls_datum_t * cert = gnutls_certificate_get_ours(state->session);
+ gnutls_x509_crt_t crt;
+
+ tlsp->ourcert = cert && import_cert(cert, &crt)==0 ? crt : NULL;
+ }
}
{
int fd, rc;
unsigned int dh_bits;
-gnutls_datum m;
+gnutls_datum_t m;
uschar filename_buf[PATH_MAX];
uschar *filename = NULL;
size_t sz;
+/* Create and install a selfsigned certificate, for use in server mode */
+
+static int
+tls_install_selfsign(exim_gnutls_state_st * state)
+{
+gnutls_x509_crt_t cert = NULL;
+time_t now;
+gnutls_x509_privkey_t pkey = NULL;
+const uschar * where;
+int rc;
+
+where = US"initialising pkey";
+if ((rc = gnutls_x509_privkey_init(&pkey))) goto err;
+
+where = US"initialising cert";
+if ((rc = gnutls_x509_crt_init(&cert))) goto err;
+
+where = US"generating pkey";
+if ((rc = gnutls_x509_privkey_generate(pkey, GNUTLS_PK_RSA,
+#ifdef SUPPORT_PARAM_TO_PK_BITS
+ gnutls_sec_param_to_pk_bits(GNUTLS_PK_RSA, GNUTLS_SEC_PARAM_LOW),
+#else
+ 1024,
+#endif
+ 0)))
+ goto err;
+
+where = US"configuring cert";
+now = 0;
+if ( (rc = gnutls_x509_crt_set_version(cert, 3))
+ || (rc = gnutls_x509_crt_set_serial(cert, &now, sizeof(now)))
+ || (rc = gnutls_x509_crt_set_activation_time(cert, now = time(NULL)))
+ || (rc = gnutls_x509_crt_set_expiration_time(cert, now + 60 * 60)) /* 1 hr */
+ || (rc = gnutls_x509_crt_set_key(cert, pkey))
+
+ || (rc = gnutls_x509_crt_set_dn_by_oid(cert,
+ GNUTLS_OID_X520_COUNTRY_NAME, 0, "UK", 2))
+ || (rc = gnutls_x509_crt_set_dn_by_oid(cert,
+ GNUTLS_OID_X520_ORGANIZATION_NAME, 0, "Exim Developers", 15))
+ || (rc = gnutls_x509_crt_set_dn_by_oid(cert,
+ GNUTLS_OID_X520_COMMON_NAME, 0,
+ smtp_active_hostname, Ustrlen(smtp_active_hostname)))
+ )
+ goto err;
+
+where = US"signing cert";
+if ((rc = gnutls_x509_crt_sign(cert, cert, pkey))) goto err;
+
+where = US"installing selfsign cert";
+ /* Since: 2.4.0 */
+if ((rc = gnutls_certificate_set_x509_key(state->x509_cred, &cert, 1, pkey)))
+ goto err;
+
+rc = OK;
+
+out:
+ if (cert) gnutls_x509_crt_deinit(cert);
+ if (pkey) gnutls_x509_privkey_deinit(pkey);
+ return rc;
+
+err:
+ rc = tls_error(where, gnutls_strerror(rc), NULL);
+ goto out;
+}
+
+
+
+
/*************************************************
* Variables re-expanded post-SNI *
*************************************************/
/* We check for tls_sni *before* expansion. */
if (!host) /* server */
- {
if (!state->received_sni)
{
if (state->tls_certificate &&
saved_tls_verify_certificates = state->exp_tls_verify_certificates;
saved_tls_crl = state->exp_tls_crl;
}
- }
rc = gnutls_certificate_allocate_credentials(&state->x509_cred);
exim_gnutls_err_check(US"gnutls_certificate_allocate_credentials");
/* certificate is mandatory in server, optional in client */
-if ((state->exp_tls_certificate == NULL) ||
- (*state->exp_tls_certificate == '\0'))
- {
+if ( !state->exp_tls_certificate
+ || !*state->exp_tls_certificate
+ )
if (!host)
- return tls_error(US"no TLS server certificate is specified", NULL, NULL);
+ return tls_install_selfsign(state);
else
DEBUG(D_tls) debug_printf("TLS: no client certificate specified; okay\n");
- }
if (state->tls_privatekey && !expand_check_tlsvar(tls_privatekey))
return DEFER;
state->exp_tls_certificate, state->exp_tls_privatekey);
if (state->received_sni)
- {
- if ((Ustrcmp(state->exp_tls_certificate, saved_tls_certificate) == 0) &&
- (Ustrcmp(state->exp_tls_privatekey, saved_tls_privatekey) == 0))
+ if ( Ustrcmp(state->exp_tls_certificate, saved_tls_certificate) == 0
+ && Ustrcmp(state->exp_tls_privatekey, saved_tls_privatekey) == 0
+ )
{
DEBUG(D_tls) debug_printf("TLS SNI: cert and key unchanged\n");
}
{
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,
/* Set the OCSP stapling server info */
-#ifdef EXPERIMENTAL_OCSP
+#ifndef DISABLE_OCSP
if ( !host /* server */
&& tls_ocsp_file
)
{
- uschar * expanded;
- int rc;
+ if (gnutls_buggy_ocsp)
+ {
+ DEBUG(D_tls) debug_printf("GnuTLS library is buggy for OCSP; avoiding\n");
+ }
+ else
+ {
+ if (!expand_check(tls_ocsp_file, US"tls_ocsp_file",
+ &state->exp_tls_ocsp_file))
+ return DEFER;
- if (!expand_check(tls_ocsp_file, US"tls_ocsp_file", &expanded))
- 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. */
- /* Lazy way; would like callback to emit debug on actual response */
+ gnutls_certificate_set_ocsp_status_request_function(state->x509_cred,
+ server_ocsp_stapling_cb, state->exp_tls_ocsp_file);
- rc = gnutls_certificate_set_ocsp_status_request_file(state->x509_cred,
- expanded, 0);
- exim_gnutls_err_check(US"gnutls_certificate_set_ocsp_status_request_file");
- DEBUG(D_tls) debug_printf("Set OCSP response file %s\n", expanded);
+ DEBUG(D_tls) debug_printf("OCSP response file = %s\n", state->exp_tls_ocsp_file);
+ }
}
#endif
{
if (!expand_check_tlsvar(tls_verify_certificates))
return DEFER;
+#ifndef SUPPORT_SYSDEFAULT_CABUNDLE
+ if (Ustrcmp(state->exp_tls_verify_certificates, "system") == 0)
+ state->exp_tls_verify_certificates = NULL;
+#endif
if (state->tls_crl && *state->tls_crl)
if (!expand_check_tlsvar(tls_crl))
return DEFER;
return OK;
}
-if (Ustat(state->exp_tls_verify_certificates, &statbuf) < 0)
+#ifdef SUPPORT_SYSDEFAULT_CABUNDLE
+if (Ustrcmp(state->exp_tls_verify_certificates, "system") == 0)
+ cert_count = gnutls_certificate_set_x509_system_trust(state->x509_cred);
+else
+#endif
{
- log_write(0, LOG_MAIN|LOG_PANIC, "could not stat %s "
- "(tls_verify_certificates): %s", state->exp_tls_verify_certificates,
- strerror(errno));
- return DEFER;
- }
+ if (Ustat(state->exp_tls_verify_certificates, &statbuf) < 0)
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC, "could not stat %s "
+ "(tls_verify_certificates): %s", state->exp_tls_verify_certificates,
+ strerror(errno));
+ return DEFER;
+ }
-/* The test suite passes in /dev/null; we could check for that path explicitly,
-but who knows if someone has some weird FIFO which always dumps some certs, or
-other weirdness. The thing we really want to check is that it's not a
-directory, since while OpenSSL supports that, GnuTLS does not.
-So s/!S_ISREG/S_ISDIR/ and change some messsaging ... */
-if (S_ISDIR(statbuf.st_mode))
- {
- DEBUG(D_tls)
- debug_printf("verify certificates path is a dir: \"%s\"\n",
- state->exp_tls_verify_certificates);
- log_write(0, LOG_MAIN|LOG_PANIC,
- "tls_verify_certificates \"%s\" is a directory",
- state->exp_tls_verify_certificates);
- return DEFER;
- }
+#ifndef SUPPORT_CA_DIR
+ /* The test suite passes in /dev/null; we could check for that path explicitly,
+ but who knows if someone has some weird FIFO which always dumps some certs, or
+ other weirdness. The thing we really want to check is that it's not a
+ directory, since while OpenSSL supports that, GnuTLS does not.
+ So s/!S_ISREG/S_ISDIR/ and change some messaging ... */
+ if (S_ISDIR(statbuf.st_mode))
+ {
+ DEBUG(D_tls)
+ debug_printf("verify certificates path is a dir: \"%s\"\n",
+ state->exp_tls_verify_certificates);
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "tls_verify_certificates \"%s\" is a directory",
+ state->exp_tls_verify_certificates);
+ return DEFER;
+ }
+#endif
-DEBUG(D_tls) debug_printf("verify certificates = %s size=" OFF_T_FMT "\n",
- state->exp_tls_verify_certificates, statbuf.st_size);
+ DEBUG(D_tls) debug_printf("verify certificates = %s size=" OFF_T_FMT "\n",
+ state->exp_tls_verify_certificates, statbuf.st_size);
-if (statbuf.st_size == 0)
- {
- DEBUG(D_tls)
- debug_printf("cert file empty, no certs, no verification, ignoring any CRL\n");
- return OK;
+ if (statbuf.st_size == 0)
+ {
+ DEBUG(D_tls)
+ debug_printf("cert file empty, no certs, no verification, ignoring any CRL\n");
+ return OK;
+ }
+
+ cert_count =
+
+#ifdef SUPPORT_CA_DIR
+ (statbuf.st_mode & S_IFMT) == S_IFDIR
+ ?
+ gnutls_certificate_set_x509_trust_dir(state->x509_cred,
+ CS state->exp_tls_verify_certificates, GNUTLS_X509_FMT_PEM)
+ :
+#endif
+ gnutls_certificate_set_x509_trust_file(state->x509_cred,
+ CS state->exp_tls_verify_certificates, GNUTLS_X509_FMT_PEM);
}
-cert_count = gnutls_certificate_set_x509_trust_file(state->x509_cred,
- CS state->exp_tls_verify_certificates, GNUTLS_X509_FMT_PEM);
if (cert_count < 0)
{
rc = cert_count;
- exim_gnutls_err_check(US"gnutls_certificate_set_x509_trust_file");
+ exim_gnutls_err_check(US"setting certificate trust");
}
DEBUG(D_tls) debug_printf("Added %d certificate authorities.\n", cert_count);
* Initialize for GnuTLS *
*************************************************/
+
+#ifndef DISABLE_OCSP
+
+static BOOL
+tls_is_buggy_ocsp(void)
+{
+const uschar * s;
+uschar maj, mid, mic;
+
+s = CUS gnutls_check_version(NULL);
+maj = atoi(CCS s);
+if (maj == 3)
+ {
+ while (*s && *s != '.') s++;
+ mid = atoi(CCS ++s);
+ if (mid <= 2)
+ return TRUE;
+ else if (mid >= 5)
+ return FALSE;
+ else
+ {
+ while (*s && *s != '.') s++;
+ mic = atoi(CCS ++s);
+ return mic <= (mid == 3 ? 16 : 3);
+ }
+ }
+return FALSE;
+}
+
+#endif
+
+
/* Called from both server and client code. In the case of a server, errors
before actual TLS negotiation return DEFER.
}
#endif
+#ifndef DISABLE_OCSP
+ if (tls_ocsp_file && (gnutls_buggy_ocsp = tls_is_buggy_ocsp()))
+ log_write(0, LOG_MAIN, "OCSP unusable with this GnuTLS library version");
+#endif
+
exim_gnutls_base_init_done = TRUE;
}
/* set SNI in client, only */
if (host)
{
- if (!expand_check(state->tlsp->sni, US"tls_out_sni", &state->exp_tls_sni))
+ if (!expand_check(sni, US"tls_out_sni", &state->tlsp->sni))
return DEFER;
- if (state->exp_tls_sni && *state->exp_tls_sni)
+ if (state->tlsp->sni && *state->tlsp->sni)
{
DEBUG(D_tls)
- debug_printf("Setting TLS client SNI to \"%s\"\n", state->exp_tls_sni);
- sz = Ustrlen(state->exp_tls_sni);
+ debug_printf("Setting TLS client SNI to \"%s\"\n", state->tlsp->sni);
+ sz = Ustrlen(state->tlsp->sni);
rc = gnutls_server_name_set(state->session,
- GNUTLS_NAME_DNS, state->exp_tls_sni, sz);
+ GNUTLS_NAME_DNS, state->tlsp->sni, sz);
exim_gnutls_err_check(US"gnutls_server_name_set");
}
}
-
/*************************************************
* Extract peer information *
*************************************************/
peer_status(exim_gnutls_state_st *state)
{
uschar cipherbuf[256];
-const gnutls_datum *cert_list;
+const gnutls_datum_t *cert_list;
int old_pool, rc;
unsigned int cert_list_size = 0;
gnutls_protocol_t protocol;
{
DEBUG(D_tls) debug_printf("TLS: no certificate from peer (%p & %d)\n",
cert_list, cert_list_size);
- if (state->verify_requirement == VERIFY_REQUIRED)
+ if (state->verify_requirement >= VERIFY_REQUIRED)
return tls_error(US"certificate verification failed",
"no certificate received from peer", state->host);
return OK;
const char *ctn = gnutls_certificate_type_get_name(ct);
DEBUG(D_tls)
debug_printf("TLS: peer cert not X.509 but instead \"%s\"\n", ctn);
- if (state->verify_requirement == VERIFY_REQUIRED)
+ if (state->verify_requirement >= VERIFY_REQUIRED)
return tls_error(US"certificate verification not possible, unhandled type",
ctn, state->host);
return OK;
}
-#define exim_gnutls_peer_err(Label) do { \
- if (rc != GNUTLS_E_SUCCESS) { \
- DEBUG(D_tls) debug_printf("TLS: peer cert problem: %s: %s\n", (Label), gnutls_strerror(rc)); \
- if (state->verify_requirement == VERIFY_REQUIRED) { return tls_error((Label), gnutls_strerror(rc), state->host); } \
- return OK; } } while (0)
+#define exim_gnutls_peer_err(Label) \
+ do { \
+ if (rc != GNUTLS_E_SUCCESS) \
+ { \
+ DEBUG(D_tls) debug_printf("TLS: peer cert problem: %s: %s\n", \
+ (Label), gnutls_strerror(rc)); \
+ if (state->verify_requirement >= VERIFY_REQUIRED) \
+ return tls_error((Label), gnutls_strerror(rc), state->host); \
+ return OK; \
+ } \
+ } while (0)
-rc = gnutls_x509_crt_init(&crt);
-exim_gnutls_peer_err(US"gnutls_x509_crt_init (crt)");
+rc = import_cert(&cert_list[0], &crt);
+exim_gnutls_peer_err(US"cert 0");
+
+state->tlsp->peercert = state->peercert = crt;
-rc = gnutls_x509_crt_import(crt, &cert_list[0], GNUTLS_X509_FMT_DER);
-exim_gnutls_peer_err(US"failed to import certificate [gnutls_x509_crt_import(cert 0)]");
sz = 0;
rc = gnutls_x509_crt_get_dn(crt, NULL, &sz);
if (rc != GNUTLS_E_SHORT_MEMORY_BUFFER)
dn_buf = store_get_perm(sz);
rc = gnutls_x509_crt_get_dn(crt, CS dn_buf, &sz);
exim_gnutls_peer_err(US"failed to extract certificate DN [gnutls_x509_crt_get_dn(cert 0)]");
+
state->peerdn = dn_buf;
return OK;
/* Handle the result of verification. INVALID seems to be set as well
as REVOKED, but leave the test for both. */
-if (rc < 0 || verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED))
+if (rc < 0 ||
+ verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED)
+ )
{
state->peer_cert_verified = FALSE;
if (!*error)
? "certificate revoked" : "certificate invalid";
DEBUG(D_tls)
- debug_printf("TLS certificate verification failed (%s): peerdn=%s\n",
+ debug_printf("TLS certificate verification failed (%s): peerdn=\"%s\"\n",
*error, state->peerdn ? state->peerdn : US"<unset>");
- if (state->verify_requirement == VERIFY_REQUIRED)
+ if (state->verify_requirement >= VERIFY_REQUIRED)
{
- gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE);
+ gnutls_alert_send(state->session,
+ GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE);
return FALSE;
}
DEBUG(D_tls)
debug_printf("TLS verify failure overridden (host in tls_try_verify_hosts)\n");
}
+
else
{
+ if (state->exp_tls_verify_cert_hostnames)
+ {
+ int sep = 0;
+ const uschar * list = state->exp_tls_verify_cert_hostnames;
+ uschar * name;
+ while ((name = string_nextinlist(&list, &sep, NULL, 0)))
+ if (gnutls_x509_crt_check_hostname(state->tlsp->peercert, CS name))
+ break;
+ if (!name)
+ {
+ DEBUG(D_tls)
+ debug_printf("TLS certificate verification failed: cert name mismatch\n");
+ if (state->verify_requirement >= VERIFY_REQUIRED)
+ {
+ gnutls_alert_send(state->session,
+ GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE);
+ return FALSE;
+ }
+ return TRUE;
+ }
+ }
state->peer_cert_verified = TRUE;
- DEBUG(D_tls) debug_printf("TLS certificate verified: peerdn=%s\n",
+ DEBUG(D_tls) debug_printf("TLS certificate verified: peerdn=\"%s\"\n",
state->peerdn ? state->peerdn : US"<unset>");
}
+#ifndef DISABLE_OCSP
+
+static int
+server_ocsp_stapling_cb(gnutls_session_t session, void * ptr,
+ gnutls_datum_t * ocsp_response)
+{
+int ret;
+
+if ((ret = gnutls_load_file(ptr, ocsp_response)) < 0)
+ {
+ DEBUG(D_tls) debug_printf("Failed to load ocsp stapling file %s\n",
+ (char *)ptr);
+ tls_in.ocsp = OCSP_NOT_RESP;
+ return GNUTLS_E_NO_CERTIFICATE_STATUS;
+ }
+
+tls_in.ocsp = OCSP_VFY_NOT_TRIED;
+return 0;
+}
+
+#endif
+
+
+#ifndef DISABLE_EVENT
+/*
+We use this callback to get observability and detail-level control
+for an exim TLS connection (either direction), raising a tls:cert event
+for each cert in the chain presented by the peer. Any event
+can deny verification.
+
+Return 0 for the handshake to continue or non-zero to terminate.
+*/
+
+static int
+verify_cb(gnutls_session_t session)
+{
+const gnutls_datum_t * cert_list;
+unsigned int cert_list_size = 0;
+gnutls_x509_crt_t crt;
+int rc;
+uschar * yield;
+exim_gnutls_state_st * state = gnutls_session_get_ptr(session);
+
+cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
+if (cert_list)
+ while (cert_list_size--)
+ {
+ rc = import_cert(&cert_list[cert_list_size], &crt);
+ if (rc != GNUTLS_E_SUCCESS)
+ {
+ DEBUG(D_tls) debug_printf("TLS: peer cert problem: depth %d: %s\n",
+ cert_list_size, gnutls_strerror(rc));
+ break;
+ }
+
+ state->tlsp->peercert = crt;
+ if ((yield = event_raise(state->event_action,
+ US"tls:cert", string_sprintf("%d", cert_list_size))))
+ {
+ log_write(0, LOG_MAIN,
+ "SSL verify denied by event-action: depth=%d: %s",
+ cert_list_size, yield);
+ return 1; /* reject */
+ }
+ state->tlsp->peercert = NULL;
+ }
+
+return 0;
+}
+
+#endif
+
+
/* ------------------------------------------------------------------------ */
/* Exported functions */
if (verify_check_host(&tls_verify_hosts) == OK)
{
- DEBUG(D_tls) debug_printf("TLS: a client certificate will be required.\n");
+ DEBUG(D_tls)
+ debug_printf("TLS: a client certificate will be required.\n");
state->verify_requirement = VERIFY_REQUIRED;
gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE);
}
else if (verify_check_host(&tls_try_verify_hosts) == OK)
{
- DEBUG(D_tls) debug_printf("TLS: a client certificate will be requested but not required.\n");
+ DEBUG(D_tls)
+ debug_printf("TLS: a client certificate will be requested but not required.\n");
state->verify_requirement = VERIFY_OPTIONAL;
gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUEST);
}
else
{
- DEBUG(D_tls) debug_printf("TLS: a client certificate will not be requested.\n");
+ DEBUG(D_tls)
+ debug_printf("TLS: a client certificate will not be requested.\n");
state->verify_requirement = VERIFY_NONE;
gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_IGNORE);
}
+#ifndef DISABLE_EVENT
+if (event_action)
+ {
+ state->event_action = event_action;
+ gnutls_session_set_ptr(state->session, state);
+ gnutls_certificate_set_verify_function(state->x509_cred, verify_cb);
+ }
+#endif
+
/* Register SNI handling; always, even if not in tls_certificate, so that the
expansion variable $tls_sni is always available. */
if (!state->tlsp->on_connect)
{
smtp_printf("220 TLS go ahead\r\n");
- fflush(smtp_out); /*XXX JGH */
+ fflush(smtp_out);
}
/* Now negotiate the TLS session. We put our own timer on it, since it seems
that the GnuTLS library doesn't. */
gnutls_transport_set_ptr2(state->session,
- (gnutls_transport_ptr)fileno(smtp_in),
- (gnutls_transport_ptr)fileno(smtp_out));
+ (gnutls_transport_ptr_t)(long) fileno(smtp_in),
+ (gnutls_transport_ptr_t)(long) fileno(smtp_out));
state->fd_in = fileno(smtp_in);
state->fd_out = fileno(smtp_out);
sigalrm_seen = FALSE;
if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
do
- {
rc = gnutls_handshake(state->session);
- } while ((rc == GNUTLS_E_AGAIN) ||
- (rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen));
+while (rc == GNUTLS_E_AGAIN || rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen);
alarm(0);
if (rc != GNUTLS_E_SUCCESS)
/* Verify after the fact */
-if (state->verify_requirement != VERIFY_NONE)
+if ( state->verify_requirement != VERIFY_NONE
+ && !verify_certificate(state, &error))
{
- if (!verify_certificate(state, &error))
+ if (state->verify_requirement != VERIFY_OPTIONAL)
{
- if (state->verify_requirement == VERIFY_OPTIONAL)
- {
- DEBUG(D_tls)
- debug_printf("TLS: continuing on only because verification was optional, after: %s\n",
- error);
- }
- else
- {
- tls_error(US"certificate verification failed", error, NULL);
- return FAIL;
- }
+ tls_error(US"certificate verification failed", error, NULL);
+ return FAIL;
}
+ DEBUG(D_tls)
+ debug_printf("TLS: continuing on only because verification was optional, after: %s\n",
+ error);
}
/* Figure out peer DN, and if authenticated, etc. */
/* Sets various Exim expansion variables; always safe within server */
-extract_exim_vars_from_tls_state(state, TRUE);
+extract_exim_vars_from_tls_state(state);
/* TLS has been set up. Adjust the input functions to read via TLS,
and initialize appropriately. */
state->xfer_buffer = store_malloc(ssl_xfer_buffer_size);
receive_getc = tls_getc;
+receive_get_cache = tls_get_cache;
receive_ungetc = tls_ungetc;
receive_feof = tls_feof;
receive_ferror = tls_ferror;
+static void
+tls_client_setup_hostname_checks(host_item * host, exim_gnutls_state_st * state,
+ smtp_transport_options_block * ob)
+{
+if (verify_check_given_host(&ob->tls_verify_cert_hostnames, host) == OK)
+ {
+ state->exp_tls_verify_cert_hostnames =
+#ifdef SUPPORT_I18N
+ string_domain_utf8_to_alabel(host->name, NULL);
+#else
+ host->name;
+#endif
+ DEBUG(D_tls)
+ debug_printf("TLS: server cert verification includes hostname: \"%s\".\n",
+ state->exp_tls_verify_cert_hostnames);
+ }
+}
+
+
/*************************************************
* Start a TLS session in a client *
*************************************************/
fd the fd of the connection
host connected host (for messages)
addr the first address (not used)
- certificate certificate file
- privatekey private key file
- sni TLS SNI to send to remote host
- verify_certs file for certificate verify
- verify_crl CRL for verify
- require_ciphers list of allowed ciphers or NULL
- hosts_require_ocsp hosts for which to request certificate-status (OCSP)
- dh_min_bits minimum number of bits acceptable in server's DH prime
- timeout startup timeout
- verify_hosts mandatory client verification
- try_verify_hosts optional client verification
+ tb transport (always smtp)
Returns: OK/DEFER/FAIL (because using common functions),
but for a client, DEFER and FAIL have the same meaning
int
tls_client_start(int fd, host_item *host,
address_item *addr ARG_UNUSED,
- uschar *certificate, uschar *privatekey, uschar *sni,
- uschar *verify_certs, uschar *verify_crl,
- uschar *require_ciphers,
-#ifdef EXPERIMENTAL_OCSP
- uschar *hosts_require_ocsp,
+ transport_instance *tb
+#ifdef EXPERIMENTAL_DANE
+ , dns_answer * unused_tlsa_dnsa
#endif
- int dh_min_bits, int timeout,
- uschar *verify_hosts, uschar *try_verify_hosts)
+ )
{
+smtp_transport_options_block *ob =
+ (smtp_transport_options_block *)tb->options_block;
int rc;
const char *error;
exim_gnutls_state_st *state = NULL;
-#ifdef EXPERIMENTAL_OCSP
-BOOL require_ocsp = verify_check_this_host(&hosts_require_ocsp,
- NULL, host->name, host->address, NULL) == OK;
+#ifndef DISABLE_OCSP
+BOOL require_ocsp =
+ verify_check_given_host(&ob->hosts_require_ocsp, host) == OK;
+BOOL request_ocsp = require_ocsp ? TRUE
+ : verify_check_given_host(&ob->hosts_request_ocsp, host) == OK;
#endif
DEBUG(D_tls) debug_printf("initialising GnuTLS as a client on fd %d\n", fd);
-if ((rc = tls_init(host, certificate, privatekey,
- sni, verify_certs, verify_crl, require_ciphers, &state)) != OK)
+if ((rc = tls_init(host, ob->tls_certificate, ob->tls_privatekey,
+ ob->tls_sni, ob->tls_verify_certificates, ob->tls_crl,
+ ob->tls_require_ciphers, &state)) != OK)
return rc;
-if (dh_min_bits < EXIM_CLIENT_DH_MIN_MIN_BITS)
{
- DEBUG(D_tls)
- debug_printf("WARNING: tls_dh_min_bits far too low, clamping %d up to %d\n",
- dh_min_bits, EXIM_CLIENT_DH_MIN_MIN_BITS);
- dh_min_bits = EXIM_CLIENT_DH_MIN_MIN_BITS;
- }
+ int dh_min_bits = ob->tls_dh_min_bits;
+ if (dh_min_bits < EXIM_CLIENT_DH_MIN_MIN_BITS)
+ {
+ DEBUG(D_tls)
+ debug_printf("WARNING: tls_dh_min_bits far too low,"
+ " clamping %d up to %d\n",
+ dh_min_bits, EXIM_CLIENT_DH_MIN_MIN_BITS);
+ dh_min_bits = EXIM_CLIENT_DH_MIN_MIN_BITS;
+ }
-DEBUG(D_tls) debug_printf("Setting D-H prime minimum acceptable bits to %d\n",
- dh_min_bits);
-gnutls_dh_set_prime_bits(state->session, dh_min_bits);
+ DEBUG(D_tls) debug_printf("Setting D-H prime minimum"
+ " acceptable bits to %d\n",
+ dh_min_bits);
+ gnutls_dh_set_prime_bits(state->session, dh_min_bits);
+ }
-/* Stick to the old behaviour for compatibility if tls_verify_certificates is
+/* Stick to the old behaviour for compatibility if tls_verify_certificates is
set but both tls_verify_hosts and tls_try_verify_hosts are unset. Check only
the specified host patterns if one of them is defined */
-if (( state->exp_tls_verify_certificates
- && !verify_hosts
- && !try_verify_hosts
- )
- ||
- verify_check_host(&verify_hosts) == OK
+if ( ( state->exp_tls_verify_certificates
+ && !ob->tls_verify_hosts
+ && (!ob->tls_try_verify_hosts || !*ob->tls_try_verify_hosts)
+ )
+ || verify_check_given_host(&ob->tls_verify_hosts, host) == OK
)
{
- DEBUG(D_tls) debug_printf("TLS: server certificate verification required.\n");
+ tls_client_setup_hostname_checks(host, state, ob);
+ DEBUG(D_tls)
+ debug_printf("TLS: server certificate verification required.\n");
state->verify_requirement = VERIFY_REQUIRED;
gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE);
}
-else if (verify_check_host(&try_verify_hosts) == OK)
+else if (verify_check_given_host(&ob->tls_try_verify_hosts, host) == OK)
{
- DEBUG(D_tls) debug_printf("TLS: server certificate verification optional.\n");
+ tls_client_setup_hostname_checks(host, state, ob);
+ DEBUG(D_tls)
+ debug_printf("TLS: server certificate verification optional.\n");
state->verify_requirement = VERIFY_OPTIONAL;
gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUEST);
}
else
{
- DEBUG(D_tls) debug_printf("TLS: server certificate verification not required.\n");
+ DEBUG(D_tls)
+ debug_printf("TLS: server certificate verification not required.\n");
state->verify_requirement = VERIFY_NONE;
gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_IGNORE);
}
-#ifdef EXPERIMENTAL_OCSP /* since GnuTLS 3.1.3 */
-if (require_ocsp &&
- (rc = gnutls_ocsp_status_request_enable_client(state->session, NULL, 0, NULL))
- != OK)
- return tls_error(US"cert-status-req", gnutls_strerror(rc), state->host);
+#ifndef DISABLE_OCSP
+ /* supported since GnuTLS 3.1.3 */
+if (request_ocsp)
+ {
+ DEBUG(D_tls) debug_printf("TLS: will request OCSP stapling\n");
+ if ((rc = gnutls_ocsp_status_request_enable_client(state->session,
+ NULL, 0, NULL)) != OK)
+ return tls_error(US"cert-status-req",
+ gnutls_strerror(rc), state->host);
+ tls_out.ocsp = OCSP_NOT_RESP;
+ }
+#endif
+
+#ifndef DISABLE_EVENT
+if (tb->event_action)
+ {
+ state->event_action = tb->event_action;
+ gnutls_session_set_ptr(state->session, state);
+ gnutls_certificate_set_verify_function(state->x509_cred, verify_cb);
+ }
#endif
-gnutls_transport_set_ptr(state->session, (gnutls_transport_ptr)fd);
+gnutls_transport_set_ptr(state->session, (gnutls_transport_ptr_t)(long) fd);
state->fd_in = fd;
state->fd_out = fd;
+DEBUG(D_tls) debug_printf("about to gnutls_handshake\n");
/* There doesn't seem to be a built-in timeout on connection. */
sigalrm_seen = FALSE;
-alarm(timeout);
+alarm(ob->command_timeout);
do
{
rc = gnutls_handshake(state->session);
!verify_certificate(state, &error))
return tls_error(US"certificate verification failed", error, state->host);
-#ifdef EXPERIMENTAL_OCSP
+#ifndef DISABLE_OCSP
if (require_ocsp)
{
DEBUG(D_tls)
&& (rc= gnutls_ocsp_resp_print(resp, GNUTLS_OCSP_PRINT_FULL, &printed)) == 0
)
{
- fprintf(stderr, "%.4096s", printed.data);
+ debug_printf("%.4096s", printed.data);
gnutls_free(printed.data);
}
else
(void) tls_error(US"ocsp decode", gnutls_strerror(rc), state->host);
}
- fprintf(stderr, "%s: checking ocsp\n", __FUNCTION__);
if (gnutls_ocsp_status_request_is_checked(state->session, 0) == 0)
+ {
+ tls_out.ocsp = OCSP_FAILED;
return tls_error(US"certificate status check failed", NULL, state->host);
+ }
DEBUG(D_tls) debug_printf("Passed OCSP checking\n");
+ tls_out.ocsp = OCSP_VFIED;
}
#endif
/* Sets various Exim expansion variables; may need to adjust for ACL callouts */
-extract_exim_vars_from_tls_state(state, FALSE);
+extract_exim_vars_from_tls_state(state);
return OK;
}
DEBUG(D_tls) debug_printf("Got TLS_EOF\n");
receive_getc = smtp_getc;
+ receive_get_cache = smtp_get_cache;
receive_ungetc = smtp_ungetc;
receive_feof = smtp_feof;
receive_ferror = smtp_ferror;
state->tlsp->active = -1;
state->tlsp->bits = 0;
state->tlsp->certificate_verified = FALSE;
- tls_channelbinding_b64 = NULL; /*XXX JGH */
+ tls_channelbinding_b64 = NULL;
state->tlsp->cipher = NULL;
+ state->tlsp->peercert = NULL;
state->tlsp->peerdn = NULL;
return smtp_getc();
return state->xfer_buffer[state->xfer_buffer_lwm++];
}
+void
+tls_get_cache()
+{
+#ifndef DISABLE_DKIM
+exim_gnutls_state_st * state = &state_server;
+int n = state->xfer_buffer_hwm - state->xfer_buffer_lwm;
+if (n > 0)
+ dkim_exim_verify_feed(state->xfer_buffer+state->xfer_buffer_lwm, n);
+#endif
+}
+