X-Git-Url: https://vcs.fsf.org/?p=exim.git;a=blobdiff_plain;f=src%2Fsrc%2Ftls-gnu.c;h=e2ac17c8838df75d1e73f85356a2480231503d24;hp=af43686e42fde592d7711404586436264808705e;hb=98716abe2b636d275e866f3ad6374cb70bf6e504;hpb=e51c7be22dfccad376659a1a46cee93c9979bbf7 diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c index af43686e4..e2ac17c88 100644 --- a/src/src/tls-gnu.c +++ b/src/src/tls-gnu.c @@ -2,7 +2,7 @@ * 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 */ @@ -43,7 +43,24 @@ require current GnuTLS, then we'll drop support for the ancient libraries). #if GNUTLS_VERSION_NUMBER >= 0x020c00 # include #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(EXPERIMENTAL_EVENT) +# warning "GnuTLS library version too old; tls:cert event unsupported" +# undef EXPERIMENTAL_EVENT +#endif +#if GNUTLS_VERSION_NUMBER >= 0x030306 +# define SUPPORT_CA_DIR +#else +# undef SUPPORT_CA_DIR +#endif +#if GNUTLS_VERSION_NUMBER >= 0x030314 +# define SUPPORT_SYSDEFAULT_CABUNDLE +#endif + +#ifndef DISABLE_OCSP # include #endif @@ -61,11 +78,7 @@ Changes: /* Values for verify_requirement */ enum peer_verify_requirement - { VERIFY_NONE, VERIFY_OPTIONAL, VERIFY_REQUIRED -#ifdef EXPERIMENTAL_CERTNAMES - ,VERIFY_WITHHOST -#endif - }; + { 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 @@ -103,13 +116,13 @@ typedef struct exim_gnutls_state { 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; -#ifdef EXPERIMENTAL_CERTNAMES - uschar *exp_tls_verify_cert_hostnames; + const uschar *exp_tls_verify_cert_hostnames; +#ifdef EXPERIMENTAL_EVENT + uschar *event_action; #endif tls_support *tlsp; /* set in tls_init() */ @@ -126,7 +139,8 @@ static const exim_gnutls_state_st exim_gnutls_state_init = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, -#ifdef EXPERIMENTAL_CERTNAMES + NULL, +#ifdef EXPERIMENTAL_EVENT NULL, #endif NULL, @@ -140,7 +154,9 @@ context we're currently dealing with" pointer and rely upon being 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; @@ -170,18 +186,18 @@ static BOOL exim_gnutls_base_init_done = FALSE; 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 { \ @@ -216,7 +232,7 @@ static void exim_gnutls_logger_cb(int level, const char *message); static int exim_sni_handling_cb(gnutls_session_t session); -#ifdef EXPERIMENTAL_OCSP +#ifndef DISABLE_OCSP static int server_ocsp_stapling_cb(gnutls_session_t session, void * ptr, gnutls_datum_t * ocsp_response); #endif @@ -252,7 +268,7 @@ tls_error(const uschar *prefix, const char *msg, const host_item *host) { 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; } @@ -261,6 +277,7 @@ else 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; @@ -809,7 +826,7 @@ if (state->exp_tls_certificate && *state->exp_tls_certificate) /* Set the OCSP stapling server info */ -#ifdef EXPERIMENTAL_OCSP +#ifndef DISABLE_OCSP if ( !host /* server */ && tls_ocsp_file ) @@ -825,7 +842,7 @@ if ( !host /* server */ gnutls_certificate_set_ocsp_status_request_function(state->x509_cred, server_ocsp_stapling_cb, state->exp_tls_ocsp_file); - DEBUG(D_tls) debug_printf("Set OCSP response file %s\n", &state->exp_tls_ocsp_file); + DEBUG(D_tls) debug_printf("OCSP response file = %s\n", state->exp_tls_ocsp_file); } #endif @@ -840,6 +857,10 @@ if (state->tls_verify_certificates && *state->tls_verify_certificates) { 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; @@ -860,46 +881,65 @@ else 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 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; + } +#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); @@ -1081,15 +1121,15 @@ if (rc != OK) return rc; /* 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"); } } @@ -1355,11 +1395,10 @@ if (rc < 0 || else { -#ifdef EXPERIMENTAL_CERTNAMES - if (state->verify_requirement == VERIFY_WITHHOST) + if (state->exp_tls_verify_cert_hostnames) { int sep = 0; - uschar * list = state->exp_tls_verify_cert_hostnames; + 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)) @@ -1368,12 +1407,15 @@ else { DEBUG(D_tls) debug_printf("TLS certificate verification failed: cert name mismatch\n"); - gnutls_alert_send(state->session, - GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE); - return FALSE; + if (state->verify_requirement >= VERIFY_REQUIRED) + { + gnutls_alert_send(state->session, + GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE); + return FALSE; + } + return TRUE; } } -#endif state->peer_cert_verified = TRUE; DEBUG(D_tls) debug_printf("TLS certificate verified: peerdn=\"%s\"\n", state->peerdn ? state->peerdn : US""); @@ -1485,7 +1527,7 @@ return 0; -#ifdef EXPERIMENTAL_OCSP +#ifndef DISABLE_OCSP static int server_ocsp_stapling_cb(gnutls_session_t session, void * ptr, @@ -1508,6 +1550,54 @@ return 0; #endif +#ifdef EXPERIMENTAL_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 * 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 @@ -1584,6 +1674,15 @@ else gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_IGNORE); } +#ifdef EXPERIMENTAL_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. */ @@ -1680,6 +1779,25 @@ return OK; +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 EXPERIMENTAL_INTERNATIONAL + 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 * *************************************************/ @@ -1690,7 +1808,7 @@ Arguments: fd the fd of the connection host connected host (for messages) addr the first address (not used) - ob smtp transport options + tb transport (always smtp) Returns: OK/DEFER/FAIL (because using common functions), but for a client, DEFER and FAIL have the same meaning @@ -1699,18 +1817,22 @@ Returns: OK/DEFER/FAIL (because using common functions), int tls_client_start(int fd, host_item *host, address_item *addr ARG_UNUSED, - void *v_ob) + transport_instance *tb +#ifdef EXPERIMENTAL_DANE + , dne_answer * unused_tlsa_dnsa +#endif + ) { -smtp_transport_options_block *ob = v_ob; +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(&ob->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_this_host(&ob->hosts_request_ocsp, - NULL, host->name, host->address, NULL) == OK; + : 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); @@ -1741,39 +1863,22 @@ if ((rc = tls_init(host, ob->tls_certificate, ob->tls_privatekey, 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 - && !ob->tls_verify_hosts - && !ob->tls_try_verify_hosts - ) - || - verify_check_host(&ob->tls_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 ) { -#ifdef EXPERIMENTAL_CERTNAMES - if (ob->tls_verify_cert_hostnames) - { - DEBUG(D_tls) - debug_printf("TLS: server cert incl. hostname verification required.\n"); - state->verify_requirement = VERIFY_WITHHOST; - if (!expand_check(ob->tls_verify_cert_hostnames, - US"tls_verify_cert_hostnames", - &state->exp_tls_verify_cert_hostnames)) - return FAIL; - if (state->exp_tls_verify_cert_hostnames) - DEBUG(D_tls) debug_printf("Cert hostname to check: \"%s\"\n", - state->exp_tls_verify_cert_hostnames); - } - else -#endif - { - DEBUG(D_tls) - debug_printf("TLS: server certificate verification required.\n"); - state->verify_requirement = VERIFY_REQUIRED; - } + 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(&ob->tls_try_verify_hosts) == OK) +else if (verify_check_given_host(&ob->tls_try_verify_hosts, host) == OK) { + tls_client_setup_hostname_checks(host, state, ob); DEBUG(D_tls) debug_printf("TLS: server certificate verification optional.\n"); state->verify_requirement = VERIFY_OPTIONAL; @@ -1787,7 +1892,8 @@ else gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_IGNORE); } -#ifdef EXPERIMENTAL_OCSP /* since GnuTLS 3.1.3 */ +#ifndef DISABLE_OCSP + /* supported since GnuTLS 3.1.3 */ if (request_ocsp) { DEBUG(D_tls) debug_printf("TLS: will request OCSP stapling\n"); @@ -1799,6 +1905,15 @@ if (request_ocsp) } #endif +#ifdef EXPERIMENTAL_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)(long) fd); state->fd_in = fd; state->fd_out = fd; @@ -1827,7 +1942,7 @@ if (state->verify_requirement != VERIFY_NONE && !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)